Internationalisation: Mark up @grafana/sql package (#105842)

* scaffolding for package

* crowdin scaffolding

* markup

* add translations

* fix locale location

* fix tsconfig?

* undo bundler change

* object tranlsations, expose loadResources and call in mssql

* prettier

* remove useTranslate

* extract translations

* last couple of fixes

* remove deleted files
This commit is contained in:
Ashley Harrison
2025-06-12 10:52:04 +01:00
committed by GitHub
parent c42c89d5e4
commit 84eafb9a56
31 changed files with 405 additions and 89 deletions
@@ -17,6 +17,7 @@ import { isString } from 'lodash';
import { dateTime, toOption } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t } from '@grafana/i18n';
import { Button, DateTimePicker, Input, Select } from '@grafana/ui';
const buttonLabels = {
@@ -68,7 +69,7 @@ export const widgets: Widgets = {
return (
<Select
id={props.id}
aria-label="Macros value selector"
aria-label={t('components.widgets.aria-label-macros-value-selector', 'Macros value selector')}
menuShouldPortal
options={macros.map(toOption)}
value={props?.value}
@@ -124,7 +125,7 @@ export const settings: Settings = {
return (
<Select
id={conjProps?.id}
aria-label="Conjunction"
aria-label={t('components.settings.aria-label-conjunction', 'Conjunction')}
data-testid={selectors.components.SQLQueryEditor.filterConjunction}
menuShouldPortal
options={conjProps?.conjunctionOptions ? Object.keys(conjProps?.conjunctionOptions).map(toOption) : undefined}
@@ -140,7 +141,7 @@ export const settings: Settings = {
<Select
id={fieldProps?.id}
width={25}
aria-label="Field"
aria-label={t('components.settings.aria-label-field', 'Field')}
data-testid={selectors.components.SQLQueryEditor.filterField}
menuShouldPortal
options={fieldProps?.items.map((f) => {
@@ -164,7 +165,9 @@ export const settings: Settings = {
return (
<Button
type="button"
title={`${buttonProps?.label} filter`}
title={t('components.settings.title-button-filter', '{{ buttonLabel }} filter', {
buttonLabel: buttonProps?.label,
})}
onClick={buttonProps?.onClick}
variant="secondary"
size="md"
@@ -177,7 +180,7 @@ export const settings: Settings = {
return (
<Select
options={operatorProps?.items.map((op) => ({ label: op.label, value: op.key }))}
aria-label="Operator"
aria-label={t('components.settings.aria-label-operator', 'Operator')}
data-testid={selectors.components.SQLQueryEditor.filterOperator}
menuShouldPortal
value={operatorProps?.selectedKey}
@@ -301,7 +304,7 @@ function getCustomOperators(config: BasicConfig) {
sqlFormatOp: customSqlNotInFormatter,
},
[Op.MACROS]: {
label: 'Macros',
label: t('components.get-custom-operators.custom-operators.label.macros', 'Macros'),
sqlFormatOp: (field: string, _operator: string, value: string | string[] | ImmutableList<string>) => {
if (value === TIME_FILTER) {
return `$__timeFilter(${field})`;
@@ -1,6 +1,7 @@
import { useCallback } from 'react';
import { SelectableValue, toOption } from '@grafana/data';
import { t } from '@grafana/i18n';
import { AccessoryButton, EditorList, InputGroup } from '@grafana/plugin-ui';
import { Select } from '@grafana/ui';
@@ -46,12 +47,20 @@ function makeRenderColumn({ options }: { options?: Array<SelectableValue<string>
<InputGroup>
<Select
value={item.property?.name ? toOption(item.property.name) : null}
aria-label="Group by"
aria-label={t('components.make-render-column.render-column.aria-label-group-by', 'Group by')}
options={options}
menuShouldPortal
onChange={({ value }) => value && onChangeItem(setGroupByField(value))}
/>
<AccessoryButton title="Remove group by column" icon="times" variant="secondary" onClick={onDeleteItem} />
<AccessoryButton
title={t(
'components.make-render-column.render-column.title-remove-group-by-column',
'Remove group by column'
)}
icon="times"
variant="secondary"
onClick={onDeleteItem}
/>
</InputGroup>
);
};
@@ -3,6 +3,7 @@ import { useCallback } from 'react';
import * as React from 'react';
import { SelectableValue, toOption } from '@grafana/data';
import { t } from '@grafana/i18n';
import { EditorField, InputGroup } from '@grafana/plugin-ui';
import { Input, RadioButtonGroup, Select, Space } from '@grafana/ui';
@@ -59,10 +60,10 @@ export function OrderByRow({ sql, onSqlChange, columns, showOffset }: OrderByRow
return (
<>
<EditorField label="Order by" width={25}>
<EditorField label={t('components.order-by-row.label-order-by', 'Order by')} width={25}>
<InputGroup>
<Select
aria-label="Order by"
aria-label={t('components.order-by-row.aria-label-order-by', 'Order by')}
options={columns}
value={sql.orderBy?.property.name ? toOption(sql.orderBy.property.name) : null}
isClearable
@@ -80,11 +81,11 @@ export function OrderByRow({ sql, onSqlChange, columns, showOffset }: OrderByRow
/>
</InputGroup>
</EditorField>
<EditorField label="Limit" optional width={25}>
<EditorField label={t('components.order-by-row.label-limit', 'Limit')} optional width={25}>
<Input type="number" min={0} id={uniqueId('limit-')} value={sql.limit || ''} onChange={onLimitChange} />
</EditorField>
{showOffset && (
<EditorField label="Offset" optional width={25}>
<EditorField label={t('components.order-by-row.label-offset', 'Offset')} optional width={25}>
<Input type="number" id={uniqueId('offset-')} value={sql.offset || ''} onChange={onOffsetChange} />
</EditorField>
)}
@@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import { useCopyToClipboard } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { reportInteraction } from '@grafana/runtime';
import { CodeEditor, Field, IconButton, useStyles2 } from '@grafana/ui';
@@ -26,8 +27,14 @@ export function Preview({ rawSql, datasourceType }: PreviewProps) {
const labelElement = (
<div className={styles.labelWrapper}>
<span className={styles.label}>Preview</span>
<IconButton tooltip="Copy to clipboard" onClick={() => copyPreview(rawSql)} name="copy" />
<span className={styles.label}>
<Trans i18nKey="components.preview.label-element.preview">Preview</Trans>
</span>
<IconButton
tooltip={t('components.preview.label-element.tooltip-copy-to-clipboard', 'Copy to clipboard')}
onClick={() => copyPreview(rawSql)}
name="copy"
/>
</div>
);
@@ -2,6 +2,7 @@ import { useId } from 'react';
import { SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t } from '@grafana/i18n';
import { EditorField } from '@grafana/plugin-ui';
import { Select } from '@grafana/ui';
@@ -15,7 +16,7 @@ export function SelectColumn({ columns, onParameterChange, value }: Props) {
const selectInputId = useId();
return (
<EditorField label="Column" width={25}>
<EditorField label={t('components.select-column.label-column', 'Column')} width={25}>
<Select
value={value}
data-testid={selectors.components.SQLQueryEditor.selectColumn}
@@ -3,6 +3,7 @@ import { useCallback } from 'react';
import { SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t } from '@grafana/i18n';
import { Button, InlineLabel, Input, Stack, useStyles2 } from '@grafana/ui';
import { QueryEditorExpressionType } from '../../expressions';
@@ -85,11 +86,18 @@ export function SelectCustomFunctionParameters({
<Input
onChange={(e) => onParameterChange(index)(e.currentTarget.value)}
value={param.name}
aria-label={`Parameter ${index} for column ${columnIndex}`}
aria-label={t(
'components.select-custom-function-parameters.aria-label-parameter',
'Parameter {{index}} for column {{columnIndex}}',
{ index, columnIndex }
)}
data-testid={selectors.components.SQLQueryEditor.selectInputParameter}
addonAfter={
<Button
title="Remove parameter"
title={t(
'components.select-custom-function-parameters.render-parameters.params.title-remove-parameter',
'Remove parameter'
)}
type="button"
icon="times"
variant="secondary"
@@ -119,7 +127,7 @@ export function SelectCustomFunctionParameters({
variant="secondary"
size="md"
icon="plus"
title="Add parameter"
title={t('components.select-custom-function-parameters.title-add-parameter', 'Add parameter')}
/>
<InlineLabel className={styles.label}>)</InlineLabel>
</>
@@ -4,6 +4,7 @@ import { useCallback } from 'react';
import { SelectableValue, toOption } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t } from '@grafana/i18n';
import { EditorField } from '@grafana/plugin-ui';
import { Button, Select, Stack, useStyles2 } from '@grafana/ui';
@@ -29,8 +30,8 @@ export function SelectRow({ query, onQueryChange, db, columns }: SelectRowProps)
// Add necessary alias options for time series format
// when that format has been selected
if (query.format === QueryFormat.Timeseries) {
timeSeriesAliasOpts.push({ label: 'time', value: 'time' });
timeSeriesAliasOpts.push({ label: 'value', value: 'value' });
timeSeriesAliasOpts.push({ label: t('components.select-row.label.time', 'time'), value: 'time' });
timeSeriesAliasOpts.push({ label: t('components.select-row.label.value', 'value'), value: 'value' });
}
const onAggregationChange = useCallback(
@@ -92,8 +93,8 @@ export function SelectRow({ query, onQueryChange, db, columns }: SelectRowProps)
const aggregateOptions = () => {
const options: Array<SelectableValue<string>> = [
{ label: 'Aggregations', options: [] },
{ label: 'Macros', options: [] },
{ label: t('components.select-row.aggregate-options.options.label.aggregations', 'Aggregations'), options: [] },
{ label: t('components.select-row.aggregate-options.options.label.macros', 'Macros'), options: [] },
];
for (const func of db.functions()) {
// Create groups for macros
@@ -111,7 +112,11 @@ export function SelectRow({ query, onQueryChange, db, columns }: SelectRowProps)
{query.sql?.columns?.map((item, index) => (
<div key={index}>
<Stack gap={2} alignItems="end">
<EditorField label="Data operations" optional width={25}>
<EditorField
label={t('components.select-row.label-data-operations', 'Data operations')}
optional
width={25}
>
<Select
value={item.name ? toOption(item.name) : null}
inputId={`select-aggregation-${index}-${uniqueId()}`}
@@ -132,7 +137,7 @@ export function SelectRow({ query, onQueryChange, db, columns }: SelectRowProps)
db={db}
/>
<EditorField label="Alias" optional width={15}>
<EditorField label={t('components.select-row.label-alias', 'Alias')} optional width={15}>
<Select
value={item.alias ? toOption(item.alias) : null}
inputId={`select-alias-${index}-${uniqueId()}`}
@@ -145,7 +150,7 @@ export function SelectRow({ query, onQueryChange, db, columns }: SelectRowProps)
/>
</EditorField>
<Button
title="Remove column"
title={t('components.select-row.title-remove-column', 'Remove column')}
type="button"
icon="trash-alt"
variant="secondary"
@@ -159,7 +164,7 @@ export function SelectRow({ query, onQueryChange, db, columns }: SelectRowProps)
type="button"
onClick={addColumn}
variant="secondary"
title="Add column"
title={t('components.select-row.title-add-column', 'Add column')}
size="md"
icon="plus"
className={styles.addButton}
@@ -1,5 +1,6 @@
import { useAsync } from 'react-use';
import { t } from '@grafana/i18n';
import { EditorRows, EditorRow, EditorField } from '@grafana/plugin-ui';
import { DB, QueryEditorProps, QueryRowFilter } from '../../types';
@@ -31,14 +32,17 @@ export const VisualEditor = ({ query, db, queryRowFilter, onChange, onValidate,
</EditorRow>
{queryRowFilter.filter && (
<EditorRow>
<EditorField label="Filter by column value" optional>
<EditorField
label={t('components.visual-editor.label-filter-by-column-value', 'Filter by column value')}
optional
>
<SQLWhereRow fields={state.value || []} query={query} onQueryChange={onChange} db={db} />
</EditorField>
</EditorRow>
)}
{queryRowFilter.group && (
<EditorRow>
<EditorField label="Group by column">
<EditorField label={t('components.visual-editor.label-group-by-column', 'Group by column')}>
<SQLGroupByRow fields={state.value || []} query={query} onQueryChange={onChange} db={db} />
</EditorField>
</EditorRow>