Dashboards: Use i18n for texts in dynamic dashboards (#100108)

pull/100168/head
Bogdan Matei 6 months ago committed by GitHub
parent 39a6d2e586
commit c4d0114376
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 38
      .betterer.results
  2. 7
      public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx
  3. 7
      public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx
  4. 14
      public/app/features/dashboard-scene/edit-pane/VizPanelEditableElement.tsx
  5. 98
      public/app/features/dashboard-scene/scene/NavToolbarActions.tsx
  6. 19
      public/app/features/dashboard-scene/scene/layout-default/DashboardGridItemEditor.tsx
  7. 9
      public/app/features/dashboard-scene/scene/layout-default/DefaultGridLayoutManager.tsx
  8. 5
      public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridItem.tsx
  9. 34
      public/app/features/dashboard-scene/scene/layout-responsive-grid/ResponsiveGridLayoutManager.tsx
  10. 28
      public/app/features/dashboard-scene/scene/layout-rows/RowItem.tsx
  11. 15
      public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx
  12. 112
      public/locales/en-US/grafana.json
  13. 112
      public/locales/pseudo-LOCALE/grafana.json

@ -3212,10 +3212,6 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"]
],
"public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"]
],
"public/app/features/dashboard-scene/embedding/EmbeddedDashboardTestPage.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
@ -3389,40 +3385,6 @@ exports[`better eslint`] = {
"public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx:5381": [
[0, 0, 0, "\'@grafana/data/src/text/sanitize\' import is restricted from being used by a pattern. Import from the public export instead.", "0"]
],
"public/app/features/dashboard-scene/scene/NavToolbarActions.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "4"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "5"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "6"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "7"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "8"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "9"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "10"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "11"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "12"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "13"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "14"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "15"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "16"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "17"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "18"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "19"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "20"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "21"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "22"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "23"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "24"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "25"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "26"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "27"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "28"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "29"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "30"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "31"]
],
"public/app/features/dashboard-scene/scene/PanelLinks.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"]
],

@ -5,6 +5,7 @@ import { useEffect, useRef } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { SceneObjectState, SceneObjectBase, SceneObject, sceneGraph, useSceneObjectState } from '@grafana/scenes';
import { ElementSelectionContextItem, ElementSelectionContextState, ToolbarButton, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { isInCloneChain } from '../utils/clone';
import { getDashboardSceneFor } from '../utils/utils';
@ -165,12 +166,12 @@ export function DashboardEditPaneRenderer({ editPane, isCollapsed, onToggleColla
<>
<div className={styles.expandOptionsWrapper}>
<ToolbarButton
tooltip={'Open options pane'}
icon={'arrow-to-right'}
tooltip={t('dashboard.edit-pane.open', 'Open options pane')}
icon="arrow-to-right"
onClick={onToggleCollapse}
variant="canvas"
className={styles.rotate180}
aria-label={'Open options pane'}
aria-label={t('dashboard.edit-pane.open', 'Open options pane')}
/>
</div>

@ -1,6 +1,7 @@
import { useMemo } from 'react';
import { Input, TextArea } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
@ -22,13 +23,13 @@ export class DashboardEditableElement implements EditableDashboardElement {
const dashboardOptions = useMemo(() => {
return new OptionsPaneCategoryDescriptor({
title: 'Dashboard options',
title: t('dashboard.options.title', 'Dashboard options'),
id: 'dashboard-options',
isOpenDefault: true,
})
.addItem(
new OptionsPaneItemDescriptor({
title: 'Title',
title: t('dashboard.options.title-option', 'Title'),
render: function renderTitle() {
return <DashboardTitleInput dashboard={dashboard} />;
},
@ -36,7 +37,7 @@ export class DashboardEditableElement implements EditableDashboardElement {
)
.addItem(
new OptionsPaneItemDescriptor({
title: 'Description',
title: t('dashboard.options.description', 'Description'),
render: function renderTitle() {
return <DashboardDescriptionInput dashboard={dashboard} />;
},

@ -1,8 +1,8 @@
import { useMemo } from 'react';
import { ReactNode, useMemo } from 'react';
import { sceneGraph, VizPanel } from '@grafana/scenes';
import { Button } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { t, Trans } from 'app/core/internationalization';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
import { getVisualizationOptions2 } from 'app/features/dashboard/components/PanelEditor/getVisualizationOptions';
@ -29,13 +29,13 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc
const panelOptions = useMemo(() => {
return new OptionsPaneCategoryDescriptor({
title: 'Panel options',
title: t('dashboard.viz-panel.options.title', 'Panel options'),
id: 'panel-options',
isOpenDefault: true,
})
.addItem(
new OptionsPaneItemDescriptor({
title: 'Title',
title: t('dashboard.viz-panel.options.title-option', 'Title'),
value: panel.state.title,
popularRank: 1,
render: function renderTitle() {
@ -45,7 +45,7 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc
)
.addItem(
new OptionsPaneItemDescriptor({
title: 'Description',
title: t('dashboard.viz-panel.options.description', 'Description'),
value: panel.state.description,
render: function renderDescription() {
return <PanelDescriptionTextArea panel={panel} />;
@ -54,7 +54,7 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc
)
.addItem(
new OptionsPaneItemDescriptor({
title: 'Transparent background',
title: t('dashboard.viz-panel.options.transparent-background', 'Transparent background'),
render: function renderTransparent() {
return <PanelBackgroundSwitch panel={panel} />;
},
@ -104,7 +104,7 @@ export class VizPanelEditableElement implements EditableDashboardElement, BulkAc
layout.removePanel(this.panel);
};
public renderActions(): React.ReactNode {
public renderActions(): ReactNode {
return (
<>
<Button size="sm" variant="secondary">

@ -1,6 +1,5 @@
import { css } from '@emotion/css';
import { useEffect, useId, useState } from 'react';
import * as React from 'react';
import { memo, ReactNode, useEffect, useId, useState } from 'react';
import { GrafanaTheme2, store } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
@ -43,7 +42,7 @@ interface Props {
dashboard: DashboardScene;
}
export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
export const NavToolbarActions = memo<Props>(({ dashboard }) => {
const id = useId();
const actions = <ToolbarActions dashboard={dashboard} key={id} />;
@ -80,7 +79,7 @@ export function ToolbarActions({ dashboard }: Props) {
const dashboardNewLayouts = config.featureToggles.dashboardNewLayouts;
if (!isEditingPanel) {
// This adds the precence indicators in enterprise
// This adds the presence indicators in enterprise
addDynamicActions(toolbarActions, dynamicDashNavActions.left, 'left-actions');
}
@ -116,7 +115,7 @@ export function ToolbarActions({ dashboard }: Props) {
return (
<Badge
color="blue"
text="Public"
text={t('dashboard.toolbar.public-dashboard', 'Public')}
key="public-dashboard-button-badge"
className={styles.publicBadge}
data-testid={selectors.pages.Dashboard.DashNav.publicDashboardTag}
@ -134,7 +133,7 @@ export function ToolbarActions({ dashboard }: Props) {
render: () => (
<ToolbarButton
key="view-in-old-dashboard-button"
tooltip={'Switch to old dashboard page'}
tooltip={t('dashboard.toolbar.switch-old-dashboard', 'Switch to old dashboard page')}
icon="apps"
onClick={() => {
locationService.partial({ scenes: false });
@ -172,7 +171,7 @@ export function ToolbarActions({ dashboard }: Props) {
}}
data-testid={selectors.components.PageToolbar.itemButton('add_visualization')}
>
Panel
<Trans i18nKey="dashboard.toolbar.add-panel">Panel</Trans>
</Button>
),
});
@ -191,7 +190,7 @@ export function ToolbarActions({ dashboard }: Props) {
}}
data-testid={selectors.components.PageToolbar.itemButton('add_row')}
>
Row
<Trans i18nKey="dashboard.toolbar.add-row">Row</Trans>
</Button>
),
});
@ -211,7 +210,7 @@ export function ToolbarActions({ dashboard }: Props) {
DashboardInteractions.toolbarAddButtonClicked({ item: 'add_library_panel' });
}}
>
Import
<Trans i18nKey="dashboard.toolbar.add-panel-lib">Import</Trans>
</Button>
),
});
@ -363,7 +362,7 @@ export function ToolbarActions({ dashboard }: Props) {
icon="arrow-left"
data-testid={selectors.components.NavToolbar.editDashboard.backToDashboardButton}
>
Back to dashboard
<Trans i18nKey="dashboard.toolbar.back-to-dashboard">Back to dashboard</Trans>
</Button>
),
});
@ -384,7 +383,7 @@ export function ToolbarActions({ dashboard }: Props) {
icon="arrow-left"
data-testid={selectors.components.NavToolbar.editDashboard.backToDashboardButton}
>
Back to dashboard
<Trans i18nKey="dashboard.toolbar.back-to-dashboard">Back to dashboard</Trans>
</Button>
),
});
@ -396,7 +395,7 @@ export function ToolbarActions({ dashboard }: Props) {
render: () => (
<Button
key="share-dashboard-button"
tooltip={t('dashboard.toolbar.share', 'Share dashboard')}
tooltip={t('dashboard.toolbar.share.tooltip', 'Share dashboard')}
size="sm"
className={styles.buttonWithExtraMargin}
fill="outline"
@ -406,7 +405,7 @@ export function ToolbarActions({ dashboard }: Props) {
}}
data-testid={selectors.components.NavToolbar.shareDashboard}
>
Share
<Trans i18nKey="dashboard.toolbar.share.label">Share</Trans>
</Button>
),
});
@ -419,14 +418,14 @@ export function ToolbarActions({ dashboard }: Props) {
onClick={() => {
dashboard.onEnterEditMode();
}}
tooltip="Enter edit mode"
tooltip={t('dashboard.toolbar.edit.tooltip', 'Enter edit mode')}
key="edit"
className={styles.buttonWithExtraMargin}
variant={config.featureToggles.newDashboardSharingComponent ? 'secondary' : 'primary'}
size="sm"
data-testid={selectors.components.NavToolbar.editDashboard.editButton}
>
Edit
<Trans i18nKey="dashboard.toolbar.edit.label">Edit</Trans>
</Button>
),
});
@ -440,14 +439,14 @@ export function ToolbarActions({ dashboard }: Props) {
dashboard.onEnterEditMode();
dashboard.setState({ editable: true, meta: { ...meta, canEdit: true } });
}}
tooltip="This dashboard was marked as read only"
tooltip={t('dashboard.toolbar.enter-edit-mode.tooltip', 'This dashboard was marked as read only')}
key="edit"
className={styles.buttonWithExtraMargin}
variant="secondary"
size="sm"
data-testid={selectors.components.NavToolbar.editDashboard.editButton}
>
Make editable
<Trans i18nKey="dashboard.toolbar.enter-edit-mode.label">Make editable</Trans>
</Button>
),
});
@ -472,14 +471,14 @@ export function ToolbarActions({ dashboard }: Props) {
onClick={() => {
dashboard.onOpenSettings();
}}
tooltip="Dashboard settings"
tooltip={t('dashboard.toolbar.dashboard-settings.tooltip', 'Dashboard settings')}
fill="text"
size="sm"
key="settings"
variant="secondary"
data-testid={selectors.components.NavToolbar.editDashboard.settingsButton}
>
Settings
<Trans i18nKey="dashboard.toolbar.dashboard-settings.label">Settings</Trans>
</Button>
),
});
@ -490,14 +489,14 @@ export function ToolbarActions({ dashboard }: Props) {
render: () => (
<Button
onClick={() => dashboard.exitEditMode({ skipConfirm: false })}
tooltip="Exits edit mode and discards unsaved changes"
tooltip={t('dashboard.toolbar.exit-edit-mode.tooltip', 'Exits edit mode and discards unsaved changes')}
size="sm"
key="discard"
fill="text"
variant="primary"
data-testid={selectors.components.NavToolbar.editDashboard.exitButton}
>
Exit edit
<Trans i18nKey="dashboard.toolbar.exit-edit-mode.label">Exit edit</Trans>
</Button>
),
});
@ -508,7 +507,11 @@ export function ToolbarActions({ dashboard }: Props) {
render: () => (
<Button
onClick={editPanel?.onDiscard}
tooltip={editPanel?.state.isNewPanel ? 'Discard panel' : 'Discard panel changes'}
tooltip={
editPanel?.state.isNewPanel
? t('dashboard.toolbar.discard-panel-new', 'Discard panel')
: t('dashboard.toolbar.discard-panel', 'Discard panel changes')
}
size="sm"
disabled={!isEditedPanelDirty}
key="discard"
@ -516,7 +519,11 @@ export function ToolbarActions({ dashboard }: Props) {
variant="destructive"
data-testid={selectors.components.NavToolbar.editDashboard.discardChangesButton}
>
{editPanel?.state.isNewPanel ? 'Discard panel' : 'Discard panel changes'}
{editPanel?.state.isNewPanel ? (
<Trans i18nKey="dashboard.toolbar.discard-panel-new">Discard panel</Trans>
) : (
<Trans i18nKey="dashboard.toolbar.discard-panel">Discard panel changes</Trans>
)}
</Button>
),
});
@ -527,14 +534,14 @@ export function ToolbarActions({ dashboard }: Props) {
render: () => (
<Button
onClick={editPanel?.onDiscard}
tooltip="Discard library panel changes"
tooltip={t('dashboard.toolbar.discard-library-panel-changes', 'Discard library panel changes')}
size="sm"
key="discardLibraryPanel"
fill="outline"
variant="destructive"
data-testid={selectors.components.NavToolbar.editDashboard.discardChangesButton}
>
Discard library panel changes
<Trans i18nKey="dashboard.toolbar.discard-library-panel-changes">Discard library panel changes</Trans>
</Button>
),
});
@ -545,14 +552,14 @@ export function ToolbarActions({ dashboard }: Props) {
render: () => (
<Button
onClick={editPanel?.onUnlinkLibraryPanel}
tooltip="Unlink library panel"
tooltip={t('dashboard.toolbar.unlink-library-panel', 'Unlink library panel')}
size="sm"
key="unlinkLibraryPanel"
fill="outline"
variant="secondary"
data-testid={selectors.components.NavToolbar.editDashboard.unlinkLibraryPanelButton}
>
Unlink library panel
<Trans i18nKey="dashboard.toolbar.unlink-library-panel">Unlink library panel</Trans>
</Button>
),
});
@ -563,14 +570,14 @@ export function ToolbarActions({ dashboard }: Props) {
render: () => (
<Button
onClick={editPanel?.onSaveLibraryPanel}
tooltip="Save library panel"
tooltip={t('dashboard.toolbar.save-library-panel', 'Save library panel')}
size="sm"
key="saveLibraryPanel"
fill="outline"
variant="primary"
data-testid={selectors.components.NavToolbar.editDashboard.saveLibraryPanelButton}
>
Save library panel
<Trans i18nKey="dashboard.toolbar.save-library-panel">Save library panel</Trans>
</Button>
),
});
@ -587,13 +594,13 @@ export function ToolbarActions({ dashboard }: Props) {
dashboard.openSaveDrawer({});
}}
className={styles.buttonWithExtraMargin}
tooltip="Save changes"
tooltip={t('dashboard.toolbar.save-dashboard.tooltip', 'Save changes')}
key="save"
size="sm"
variant={'primary'}
variant="primary"
data-testid={selectors.components.NavToolbar.editDashboard.saveButton}
>
Save dashboard
<Trans i18nKey="dashboard.toolbar.save-dashboard.label">Save dashboard</Trans>
</Button>
);
}
@ -606,12 +613,12 @@ export function ToolbarActions({ dashboard }: Props) {
dashboard.openSaveDrawer({ saveAsCopy: true });
}}
className={styles.buttonWithExtraMargin}
tooltip="Save as copy"
tooltip={t('dashboard.toolbar.save-dashboard-copy.tooltip', 'Save as copy')}
key="save"
size="sm"
variant={isDirty ? 'primary' : 'secondary'}
>
Save as copy
<Trans i18nKey="dashboard.toolbar.save-dashboard-copy.label">Save as copy</Trans>
</Button>
);
}
@ -620,14 +627,14 @@ export function ToolbarActions({ dashboard }: Props) {
const menu = (
<Menu>
<Menu.Item
label="Save"
label={t('dashboard.toolbar.save-dashboard-short', 'Save')}
icon="save"
onClick={() => {
dashboard.openSaveDrawer({});
}}
/>
<Menu.Item
label="Save as copy"
label={t('dashboard.toolbar.save-dashboard-copy.label', 'Save as copy')}
icon="copy"
onClick={() => {
dashboard.openSaveDrawer({ saveAsCopy: true });
@ -642,16 +649,16 @@ export function ToolbarActions({ dashboard }: Props) {
onClick={() => {
dashboard.openSaveDrawer({});
}}
tooltip="Save changes"
tooltip={t('dashboard.toolbar.save-dashboard.tooltip', 'Save changes')}
size="sm"
data-testid={selectors.components.NavToolbar.editDashboard.saveButton}
variant={isDirty ? 'primary' : 'secondary'}
>
Save dashboard
<Trans i18nKey="dashboard.toolbar.save-dashboard.label">Save dashboard</Trans>
</Button>
<Dropdown overlay={menu}>
<Button
aria-label="More save options"
aria-label={t('dashboard.toolbar.more-save-options', 'More save options')}
icon="angle-down"
variant={isDirty ? 'primary' : 'secondary'}
size="sm"
@ -669,7 +676,7 @@ export function ToolbarActions({ dashboard }: Props) {
render: () => {
return (
<ToolbarButton
tooltip={'Edit dashboard v2 schema'}
tooltip={t('dashboard.toolbar.edit-dashboard-v2-schema', 'Edit dashboard v2 schema')}
icon={<Icon name="brackets-curly" size="lg" type="default" />}
key="schema-v2-button"
onClick={() => {
@ -680,21 +687,21 @@ export function ToolbarActions({ dashboard }: Props) {
},
});
const rigthActionsElements: React.ReactNode[] = renderActionElements(toolbarActions);
const leftActionsElements: React.ReactNode[] = renderActionElements(leftActions);
const rightActionsElements: ReactNode[] = renderActionElements(toolbarActions);
const leftActionsElements: ReactNode[] = renderActionElements(leftActions);
const hasActionsToLeftAndRight = showScopesSelector || leftActionsElements.length > 0;
return (
<Stack flex={1} minWidth={0} justifyContent={hasActionsToLeftAndRight ? 'space-between' : 'flex-end'}>
{showScopesSelector && <ScopesSelector />}
{leftActionsElements.length > 0 && <ToolbarButtonRow alignment="left">{leftActionsElements}</ToolbarButtonRow>}
<ToolbarButtonRow alignment="right">{rigthActionsElements}</ToolbarButtonRow>
<ToolbarButtonRow alignment="right">{rightActionsElements}</ToolbarButtonRow>
</Stack>
);
}
function renderActionElements(toolbarActions: ToolbarAction[]) {
const actionElements: React.ReactNode[] = [];
const actionElements: ReactNode[] = [];
let lastGroup = '';
for (const action of toolbarActions) {
@ -709,6 +716,7 @@ function renderActionElements(toolbarActions: ToolbarAction[]) {
actionElements.push(action.render());
lastGroup = action.group;
}
return actionElements;
}
@ -755,7 +763,7 @@ function usePanelEditDirty(panelEditor?: PanelEditor) {
interface ToolbarAction {
group: string;
condition?: boolean | string;
render: () => React.ReactNode;
render: () => ReactNode;
}
function getStyles(theme: GrafanaTheme2) {

@ -1,5 +1,6 @@
import { SelectableValue } from '@grafana/data';
import { RadioButtonGroup, Select } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
import { RepeatRowSelect2 } from 'app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect';
@ -8,23 +9,25 @@ import { DashboardGridItem } from './DashboardGridItem';
export function getDashboardGridItemOptions(gridItem: DashboardGridItem) {
const category = new OptionsPaneCategoryDescriptor({
title: 'Repeat options',
title: t('dashboard.default-layout.item-options.repeat.title', 'Repeat options'),
id: 'Repeat options',
isOpenDefault: false,
});
category.addItem(
new OptionsPaneItemDescriptor({
title: 'Repeat by variable',
description:
'Repeat this panel for each value in the selected variable. This is not visible while in edit mode. You need to go back to dashboard and then update the variable or reload the dashboard.',
title: t('dashboard.default-layout.item-options.repeat.variable.title', 'Repeat by variable'),
description: t(
'dashboard.default-layout.item-options.repeat.variable.description',
'Repeat this panel for each value in the selected variable. This is not visible while in edit mode. You need to go back to dashboard and then update the variable or reload the dashboard.'
),
render: () => <RepeatByOption gridItem={gridItem} />,
})
);
category.addItem(
new OptionsPaneItemDescriptor({
title: 'Repeat direction',
title: t('dashboard.default-layout.item-options.repeat.direction.title', 'Repeat direction'),
useShowIf: () => {
const { variableName } = gridItem.useState();
return Boolean(variableName);
@ -35,7 +38,7 @@ export function getDashboardGridItemOptions(gridItem: DashboardGridItem) {
category.addItem(
new OptionsPaneItemDescriptor({
title: 'Max per row',
title: t('dashboard.default-layout.item-options.repeat.max', 'Max per row'),
useShowIf: () => {
const { variableName, repeatDirection } = gridItem.useState();
return Boolean(variableName) && repeatDirection === 'h';
@ -55,8 +58,8 @@ function RepeatDirectionOption({ gridItem }: OptionComponentProps) {
const { repeatDirection } = gridItem.useState();
const directionOptions: Array<SelectableValue<'h' | 'v'>> = [
{ label: 'Horizontal', value: 'h' },
{ label: 'Vertical', value: 'v' },
{ label: t('dashboard.default-layout.item-options.repeat.direction.horizontal', 'Horizontal'), value: 'h' },
{ label: t('dashboard.default-layout.item-options.repeat.direction.vertical', 'Vertical'), value: 'v' },
];
return (

@ -11,6 +11,7 @@ import {
SceneGridItemLike,
} from '@grafana/scenes';
import { GRID_COLUMN_COUNT } from 'app/core/constants';
import { t } from 'app/core/internationalization';
import { isClonedKey, joinCloneKeys } from '../../utils/clone';
import {
@ -42,8 +43,12 @@ export class DefaultGridLayoutManager
public readonly isDashboardLayoutManager = true;
public static readonly descriptor = {
name: 'Default grid',
description: 'The default grid layout',
get name() {
return t('dashboard.default-layout.name', 'Default grid');
},
get description() {
return t('dashboard.default-layout.description', 'The default grid layout');
},
id: 'default-grid',
createFromLayout: DefaultGridLayoutManager.createFromLayout,
};

@ -3,6 +3,7 @@ import { css, cx } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { SceneObjectState, VizPanel, SceneObjectBase, SceneObject, SceneComponentProps } from '@grafana/scenes';
import { Switch, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
@ -35,14 +36,14 @@ export class ResponsiveGridItem extends SceneObjectBase<ResponsiveGridItemState>
const model = this;
const category = new OptionsPaneCategoryDescriptor({
title: 'Layout options',
title: t('dashboard.responsive-layout.item-options.title', 'Layout options'),
id: 'layout-options',
isOpenDefault: false,
});
category.addItem(
new OptionsPaneItemDescriptor({
title: 'Hide when no data',
title: t('dashboard.responsive-layout.item-options.hide-no-data', 'Hide when no data'),
render: function renderTransparent() {
const { hideWhenNoData } = model.useState();
return <Switch value={hideWhenNoData} id="hide-when-no-data" onChange={() => model.toggleHideWhenNoData()} />;

@ -1,6 +1,7 @@
import { SelectableValue } from '@grafana/data';
import { SceneComponentProps, SceneCSSGridLayout, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes';
import { Select } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
import { getDashboardSceneFor, getPanelIdForVizPanel, getVizPanelKeyForPanelId } from '../../utils/utils';
@ -20,8 +21,12 @@ export class ResponsiveGridLayoutManager
public readonly isDashboardLayoutManager = true;
public static readonly descriptor = {
name: 'Responsive grid',
description: 'CSS layout that adjusts to the available space',
get name() {
return t('dashboard.responsive-layout.name', 'Responsive grid');
},
get description() {
return t('dashboard.responsive-layout.description', 'CSS layout that adjusts to the available space');
},
id: 'responsive-grid',
createFromLayout: ResponsiveGridLayoutManager.createFromLayout,
};
@ -136,26 +141,35 @@ function getOptions(layoutManager: ResponsiveGridLayoutManager): OptionsPaneItem
const rowOptions: Array<SelectableValue<string>> = [];
const sizes = [100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 650];
const colOptions: Array<SelectableValue<string>> = [
{ label: `1 column`, value: `1fr` },
{ label: `2 columns`, value: `1fr 1fr` },
{ label: `3 columns`, value: `1fr 1fr 1fr` },
{ label: t('dashboard.responsive-layout.options.one-column', '1 column'), value: `1fr` },
{ label: t('dashboard.responsive-layout.options.two-columns', '2 columns'), value: `1fr 1fr` },
{ label: t('dashboard.responsive-layout.options.three-columns', '3 columns'), value: `1fr 1fr 1fr` },
];
for (const size of sizes) {
colOptions.push({ label: `Min: ${size}px`, value: `repeat(auto-fit, minmax(${size}px, auto))` });
colOptions.push({
label: t('dashboard.responsive-layout.options.min', 'Min: {{size}}px', { size }),
value: `repeat(auto-fit, minmax(${size}px, auto))`,
});
}
for (const size of sizes) {
rowOptions.push({ label: `Min: ${size}px`, value: `minmax(${size}px, auto)` });
rowOptions.push({
label: t('dashboard.responsive-layout.options.min', 'Min: {{size}}px', { size }),
value: `minmax(${size}px, auto)`,
});
}
for (const size of sizes) {
rowOptions.push({ label: `Fixed: ${size}px`, value: `${size}px` });
rowOptions.push({
label: t('dashboard.responsive-layout.options.fixed', 'Fixed: {{size}}px', { size }),
value: `${size}px`,
});
}
options.push(
new OptionsPaneItemDescriptor({
title: 'Columns',
title: t('dashboard.responsive-layout.options.columns', 'Columns'),
render: () => {
const { templateColumns } = cssLayout.useState();
return (
@ -174,7 +188,7 @@ function getOptions(layoutManager: ResponsiveGridLayoutManager): OptionsPaneItem
options.push(
new OptionsPaneItemDescriptor({
title: 'Rows',
title: t('dashboard.responsive-layout.options.rows', 'Rows'),
render: () => {
const { autoRows } = cssLayout.useState();
return (

@ -1,7 +1,7 @@
import { css, cx } from '@emotion/css';
import { ReactNode, useMemo, useRef } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import {
SceneObjectState,
@ -22,7 +22,7 @@ import {
useElementSelection,
useStyles2,
} from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { t, Trans } from 'app/core/internationalization';
import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor';
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
import { RepeatRowSelect2 } from 'app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect';
@ -66,25 +66,25 @@ export class RowItem
const rowOptions = useMemo(() => {
return new OptionsPaneCategoryDescriptor({
title: 'Row options',
title: t('dashboard.rows-layout.row-options.title', 'Row options'),
id: 'row-options',
isOpenDefault: true,
})
.addItem(
new OptionsPaneItemDescriptor({
title: 'Title',
title: t('dashboard.rows-layout.row-options.title-option', 'Title'),
render: () => <RowTitleInput row={row} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: 'Height',
title: t('dashboard.rows-layout.row-options.height.title', 'Height'),
render: () => <RowHeightSelect row={row} />,
})
)
.addItem(
new OptionsPaneItemDescriptor({
title: 'Hide row header',
title: t('dashboard.rows-layout.row-options.height.hide-row-header', 'Hide row header'),
render: () => <RowHeaderSwitch row={row} />,
})
);
@ -94,12 +94,12 @@ export class RowItem
const dashboard = getDashboardSceneFor(row);
return new OptionsPaneCategoryDescriptor({
title: 'Repeat options',
title: t('dashboard.rows-layout.row-options.repeat.title', 'Repeat options'),
id: 'row-repeat-options',
isOpenDefault: true,
}).addItem(
new OptionsPaneItemDescriptor({
title: 'Variable',
title: t('dashboard.rows-layout.row-options.repeat.variable.title', 'Variable'),
render: () => <RowRepeatSelect row={row} dashboard={dashboard} />,
})
);
@ -171,7 +171,11 @@ export class RowItem
<button
onClick={model.onCollapseToggle}
className={styles.rowTitleButton}
aria-label={isCollapsed ? 'Expand row' : 'Collapse row'}
aria-label={
isCollapsed
? t('dashboard.rows-layout.row.expand', 'Expand row')
: t('dashboard.rows-layout.row.collapse', 'Collapse row')
}
data-testid={selectors.components.DashboardRow.title(titleInterpolated!)}
>
<Icon name={isCollapsed ? 'angle-right' : 'angle-down'} />
@ -275,9 +279,9 @@ export function RowHeaderSwitch({ row }: { row: RowItem }) {
export function RowHeightSelect({ row }: { row: RowItem }) {
const { height = 'expand' } = row.useState();
const options = [
{ label: 'Expand', value: 'expand' as const },
{ label: 'Min', value: 'min' as const },
const options: Array<SelectableValue<'expand' | 'min'>> = [
{ label: t('dashboard.rows-layout.row-options.height.expand', 'Expand'), value: 'expand' },
{ label: t('dashboard.rows-layout.row-options.height.min', 'Min'), value: 'min' },
];
return (

@ -11,6 +11,7 @@ import {
VizPanel,
} from '@grafana/scenes';
import { useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { isClonedKey } from '../../utils/clone';
import { DashboardScene } from '../DashboardScene';
@ -31,8 +32,12 @@ export class RowsLayoutManager extends SceneObjectBase<RowsLayoutManagerState> i
public readonly isDashboardLayoutManager = true;
public static readonly descriptor = {
name: 'Rows',
description: 'Rows layout',
get name() {
return t('dashboard.rows-layout.name', 'Rows');
},
get description() {
return t('dashboard.rows-layout.description', 'Rows layout');
},
id: 'rows-layout',
createFromLayout: RowsLayoutManager.createFromLayout,
};
@ -63,7 +68,7 @@ export class RowsLayoutManager extends SceneObjectBase<RowsLayoutManagerState> i
rows: [
...this.state.rows,
new RowItem({
title: 'New row',
title: t('dashboard.rows-layout.row.new', 'New row'),
layout: ResponsiveGridLayoutManager.createEmpty(),
}),
],
@ -178,7 +183,7 @@ export class RowsLayoutManager extends SceneObjectBase<RowsLayoutManagerState> i
rows = config.map(
(rowConfig) =>
new RowItem({
title: rowConfig.title ?? 'Row title',
title: rowConfig.title ?? t('dashboard.rows-layout.row.new', 'New row'),
isCollapsed: !!rowConfig.isCollapsed,
layout: DefaultGridLayoutManager.fromGridItems(
rowConfig.children,
@ -189,7 +194,7 @@ export class RowsLayoutManager extends SceneObjectBase<RowsLayoutManagerState> i
})
);
} else {
rows = [new RowItem({ layout: layout.clone(), title: 'Row title' })];
rows = [new RowItem({ layout: layout.clone(), title: t('dashboard.rows-layout.row.new', 'New row') })];
}
return new RowsLayoutManager({ rows });

@ -902,6 +902,23 @@
"subtitle": "Alert rules related to this dashboard"
},
"default-layout": {
"description": "The default grid layout",
"item-options": {
"repeat": {
"direction": {
"horizontal": "Horizontal",
"title": "Repeat direction",
"vertical": "Vertical"
},
"max": "Max per row",
"title": "Repeat options",
"variable": {
"description": "Repeat this panel for each value in the selected variable. This is not visible while in edit mode. You need to go back to dashboard and then update the variable or reload the dashboard.",
"title": "Repeat by variable"
}
}
},
"name": "Default grid",
"row-actions": {
"delete": "Delete row",
"modal": {
@ -937,6 +954,7 @@
"selection-number": "No. of objects selected: "
}
},
"open": "Open options pane",
"panels": {
"multi-select": {
"selection-number": "No. of panels selected: "
@ -1016,34 +1034,118 @@
"rows": "Total number rows",
"table-title": "Stats"
},
"options": {
"description": "Description",
"title": "Dashboard options",
"title-option": "Title"
},
"panel-edit": {
"alerting-tab": {
"dashboard-not-saved": "Dashboard must be saved before alerts can be added.",
"no-rules": "There are no alert rules linked to this panel."
}
},
"responsive-layout": {
"description": "CSS layout that adjusts to the available space",
"item-options": {
"hide-no-data": "Hide when no data",
"title": "Layout options"
},
"name": "Responsive grid",
"options": {
"columns": "Columns",
"fixed": "Fixed: {{size}}px",
"min": "Min: {{size}}px",
"one-column": "1 column",
"rows": "Rows",
"three-columns": "3 columns",
"two-columns": "2 columns"
}
},
"rows-layout": {
"description": "Rows layout",
"name": "Rows",
"row": {
"collapse": "Collapse row",
"expand": "Expand row",
"new": "New row",
"repeat": {
"learn-more": "Learn more",
"warning": "Panels in this row use the {{SHARED_DASHBOARD_QUERY}} data source. These panels will reference the panel in the original row, not the ones in the repeated rows."
}
},
"row-options": {
"height": {
"expand": "Expand",
"hide-row-header": "Hide row header",
"min": "Min",
"title": "Height"
},
"repeat": {
"title": "Repeat options",
"variable": {
"title": "Variable"
}
},
"title": "Row options",
"title-option": "Title"
}
},
"toolbar": {
"add": "Add",
"add-panel": "Panel",
"add-panel-lib": "Import",
"add-row": "Row",
"alert-rules": "Alert rules",
"back-to-dashboard": "Back to dashboard",
"dashboard-settings": {
"label": "Settings",
"tooltip": "Dashboard settings"
},
"discard-library-panel-changes": "Discard library panel changes",
"discard-panel": "Discard panel changes",
"discard-panel-new": "Discard panel",
"edit": {
"label": "Edit",
"tooltip": "Enter edit mode"
},
"edit-dashboard-v2-schema": "Edit dashboard v2 schema",
"enter-edit-mode": {
"label": "Make editable",
"tooltip": "This dashboard was marked as read only"
},
"exit-edit-mode": {
"label": "Exit edit",
"tooltip": "Exits edit mode and discards unsaved changes"
},
"mark-favorite": "Mark as favorite",
"more-save-options": "More save options",
"open-original": "Open original dashboard",
"playlist-next": "Go to next dashboard",
"playlist-previous": "Go to previous dashboard",
"playlist-stop": "Stop playlist",
"public-dashboard": "Public",
"refresh": "Refresh dashboard",
"save": "Save dashboard",
"save-dashboard": {
"label": "Save dashboard",
"tooltip": "Save changes"
},
"save-dashboard-copy": {
"label": "Save as copy",
"tooltip": "Save as copy"
},
"save-dashboard-short": "Save",
"save-library-panel": "Save library panel",
"settings": "Dashboard settings",
"share": "Share dashboard",
"share": {
"label": "Share",
"tooltip": "Share dashboard"
},
"share-button": "Share",
"show-hidden-elements": "Show hidden",
"switch-old-dashboard": "Switch to old dashboard page",
"unlink-library-panel": "Unlink library panel",
"unmark-favorite": "Unmark as favorite"
},
"validation": {
@ -1051,6 +1153,14 @@
"invalid-json": "Not valid JSON",
"tags-expected-array": "tags expected array",
"tags-expected-strings": "tags expected array of strings"
},
"viz-panel": {
"options": {
"description": "Description",
"title": "Panel options",
"title-option": "Title",
"transparent-background": "Transparent background"
}
}
},
"dashboard-import": {

@ -902,6 +902,23 @@
"subtitle": "Åľęřŧ řūľęş řęľäŧęđ ŧő ŧĥįş đäşĥþőäřđ"
},
"default-layout": {
"description": "Ŧĥę đęƒäūľŧ ģřįđ ľäyőūŧ",
"item-options": {
"repeat": {
"direction": {
"horizontal": "Ħőřįžőʼnŧäľ",
"title": "Ŗępęäŧ đįřęčŧįőʼn",
"vertical": "Vęřŧįčäľ"
},
"max": "Mäχ pęř řőŵ",
"title": "Ŗępęäŧ őpŧįőʼnş",
"variable": {
"description": "Ŗępęäŧ ŧĥįş päʼnęľ ƒőř ęäčĥ väľūę įʼn ŧĥę şęľęčŧęđ väřįäþľę. Ŧĥįş įş ʼnőŧ vįşįþľę ŵĥįľę įʼn ęđįŧ mőđę. Ÿőū ʼnęęđ ŧő ģő þäčĸ ŧő đäşĥþőäřđ äʼnđ ŧĥęʼn ūpđäŧę ŧĥę väřįäþľę őř řęľőäđ ŧĥę đäşĥþőäřđ.",
"title": "Ŗępęäŧ þy väřįäþľę"
}
}
},
"name": "Đęƒäūľŧ ģřįđ",
"row-actions": {
"delete": "Đęľęŧę řőŵ",
"modal": {
@ -937,6 +954,7 @@
"selection-number": "Ńő. őƒ őþĵęčŧş şęľęčŧęđ: "
}
},
"open": "Øpęʼn őpŧįőʼnş päʼnę",
"panels": {
"multi-select": {
"selection-number": "Ńő. őƒ päʼnęľş şęľęčŧęđ: "
@ -1016,34 +1034,118 @@
"rows": "Ŧőŧäľ ʼnūmþęř řőŵş",
"table-title": "Ŝŧäŧş"
},
"options": {
"description": "Đęşčřįpŧįőʼn",
"title": "Đäşĥþőäřđ őpŧįőʼnş",
"title-option": "Ŧįŧľę"
},
"panel-edit": {
"alerting-tab": {
"dashboard-not-saved": "Đäşĥþőäřđ mūşŧ þę şävęđ þęƒőřę äľęřŧş čäʼn þę äđđęđ.",
"no-rules": "Ŧĥęřę äřę ʼnő äľęřŧ řūľęş ľįʼnĸęđ ŧő ŧĥįş päʼnęľ."
}
},
"responsive-layout": {
"description": "CŜŜ ľäyőūŧ ŧĥäŧ äđĵūşŧş ŧő ŧĥę äväįľäþľę şpäčę",
"item-options": {
"hide-no-data": "Ħįđę ŵĥęʼn ʼnő đäŧä",
"title": "Ŀäyőūŧ őpŧįőʼnş"
},
"name": "Ŗęşpőʼnşįvę ģřįđ",
"options": {
"columns": "Cőľūmʼnş",
"fixed": "Fįχęđ: {{size}}pχ",
"min": "Mįʼn: {{size}}pχ",
"one-column": "1 čőľūmʼn",
"rows": "Ŗőŵş",
"three-columns": "3 čőľūmʼnş",
"two-columns": "2 čőľūmʼnş"
}
},
"rows-layout": {
"description": "Ŗőŵş ľäyőūŧ",
"name": "Ŗőŵş",
"row": {
"collapse": "Cőľľäpşę řőŵ",
"expand": "Ēχpäʼnđ řőŵ",
"new": "Ńęŵ řőŵ",
"repeat": {
"learn-more": "Ŀęäřʼn mőřę",
"warning": "Päʼnęľş įʼn ŧĥįş řőŵ ūşę ŧĥę {{SHARED_DASHBOARD_QUERY}} đäŧä şőūřčę. Ŧĥęşę päʼnęľş ŵįľľ řęƒęřęʼnčę ŧĥę päʼnęľ įʼn ŧĥę őřįģįʼnäľ řőŵ, ʼnőŧ ŧĥę őʼnęş įʼn ŧĥę řępęäŧęđ řőŵş."
}
},
"row-options": {
"height": {
"expand": "Ēχpäʼnđ",
"hide-row-header": "Ħįđę řőŵ ĥęäđęř",
"min": "Mįʼn",
"title": "Ħęįģĥŧ"
},
"repeat": {
"title": "Ŗępęäŧ őpŧįőʼnş",
"variable": {
"title": "Väřįäþľę"
}
},
"title": "Ŗőŵ őpŧįőʼnş",
"title-option": "Ŧįŧľę"
}
},
"toolbar": {
"add": "Åđđ",
"add-panel": "Päʼnęľ",
"add-panel-lib": "Ĩmpőřŧ",
"add-row": "Ŗőŵ",
"alert-rules": "Åľęřŧ řūľęş",
"back-to-dashboard": "ßäčĸ ŧő đäşĥþőäřđ",
"dashboard-settings": {
"label": "Ŝęŧŧįʼnģş",
"tooltip": "Đäşĥþőäřđ şęŧŧįʼnģş"
},
"discard-library-panel-changes": "Đįşčäřđ ľįþřäřy päʼnęľ čĥäʼnģęş",
"discard-panel": "Đįşčäřđ päʼnęľ čĥäʼnģęş",
"discard-panel-new": "Đįşčäřđ päʼnęľ",
"edit": {
"label": "Ēđįŧ",
"tooltip": "Ēʼnŧęř ęđįŧ mőđę"
},
"edit-dashboard-v2-schema": "Ēđįŧ đäşĥþőäřđ v2 şčĥęmä",
"enter-edit-mode": {
"label": "Mäĸę ęđįŧäþľę",
"tooltip": "Ŧĥįş đäşĥþőäřđ ŵäş mäřĸęđ äş řęäđ őʼnľy"
},
"exit-edit-mode": {
"label": "Ēχįŧ ęđįŧ",
"tooltip": "Ēχįŧş ęđįŧ mőđę äʼnđ đįşčäřđş ūʼnşävęđ čĥäʼnģęş"
},
"mark-favorite": "Mäřĸ äş ƒävőřįŧę",
"more-save-options": "Mőřę şävę őpŧįőʼnş",
"open-original": "Øpęʼn őřįģįʼnäľ đäşĥþőäřđ",
"playlist-next": "Ğő ŧő ʼnęχŧ đäşĥþőäřđ",
"playlist-previous": "Ğő ŧő přęvįőūş đäşĥþőäřđ",
"playlist-stop": "Ŝŧőp pľäyľįşŧ",
"public-dashboard": "Pūþľįč",
"refresh": "Ŗęƒřęşĥ đäşĥþőäřđ",
"save": "Ŝävę đäşĥþőäřđ",
"save-dashboard": {
"label": "Ŝävę đäşĥþőäřđ",
"tooltip": "Ŝävę čĥäʼnģęş"
},
"save-dashboard-copy": {
"label": "Ŝävę äş čőpy",
"tooltip": "Ŝävę äş čőpy"
},
"save-dashboard-short": "Ŝävę",
"save-library-panel": "Ŝävę ľįþřäřy päʼnęľ",
"settings": "Đäşĥþőäřđ şęŧŧįʼnģş",
"share": "Ŝĥäřę đäşĥþőäřđ",
"share": {
"label": "Ŝĥäřę",
"tooltip": "Ŝĥäřę đäşĥþőäřđ"
},
"share-button": "Ŝĥäřę",
"show-hidden-elements": "Ŝĥőŵ ĥįđđęʼn",
"switch-old-dashboard": "Ŝŵįŧčĥ ŧő őľđ đäşĥþőäřđ päģę",
"unlink-library-panel": "Ůʼnľįʼnĸ ľįþřäřy päʼnęľ",
"unmark-favorite": "Ůʼnmäřĸ äş ƒävőřįŧę"
},
"validation": {
@ -1051,6 +1153,14 @@
"invalid-json": "Ńőŧ väľįđ ĴŜØŃ",
"tags-expected-array": "ŧäģş ęχpęčŧęđ äřřäy",
"tags-expected-strings": "ŧäģş ęχpęčŧęđ äřřäy őƒ şŧřįʼnģş"
},
"viz-panel": {
"options": {
"description": "Đęşčřįpŧįőʼn",
"title": "Päʼnęľ őpŧįőʼnş",
"title-option": "Ŧįŧľę",
"transparent-background": "Ŧřäʼnşpäřęʼnŧ þäčĸģřőūʼnđ"
}
}
},
"dashboard-import": {

Loading…
Cancel
Save