feat: add i18n support for scaffolder-react plugin
Signed-off-by: JounQin <admin@1stg.me>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
'@backstage/plugin-scaffolder-react': patch
|
||||
---
|
||||
|
||||
Add i18n support for scaffolder-react plugin
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
"build:all": "backstage-cli repo build --all",
|
||||
"build:api-docs": "LANG=en_EN yarn build:api-reports --docs --exclude 'plugins/@(adr|adr-backend|adr-common|airbrake|airbrake-backend|allure|analytics-module-ga|analytics-module-ga4|analytics-module-newrelic-browser|apache-airflow|api-docs|api-docs-module-protoc-gen-doc|apollo-explorer|app-visualizer|azure-devops|azure-devops-backend|azure-devops-common|azure-sites|azure-sites-backend|azure-sites-common|badges|badges-backend|bazaar|bazaar-backend|bitbucket-cloud-common|bitrise|catalog-graph|catalog-graphql|catalog-import|catalog-unprocessed-entities|cicd-statistics|cicd-statistics-module-gitlab|circleci|cloudbuild|code-climate|code-coverage|code-coverage-backend|codescene|config-schema|cost-insights|cost-insights-common|dynatrace|entity-feedback|entity-feedback-backend|entity-feedback-common|entity-validation|example-todo-list|example-todo-list-backend|example-todo-list-common|firehydrant|fossa|gcalendar|gcp-projects|git-release-manager|github-actions|github-deployments|github-issues|github-pull-requests-board|gitops-profiles|gocd|graphiql|graphql-backend|graphql-voyager|ilert|jenkins|jenkins-backend|jenkins-common|kafka|kafka-backend|lighthouse|lighthouse-backend|lighthouse-common|linguist|linguist-backend|linguist-common|microsoft-calendar|newrelic|newrelic-dashboard|nomad|nomad-backend|octopus-deploy|opencost|pagerduty|periskop|periskop-backend|playlist|playlist-backend|playlist-common|proxy-backend|puppetdb|rollbar|rollbar-backend|sentry|shortcuts|splunk-on-call|stack-overflow|stack-overflow-backend|stackstorm|tech-radar|tech-radar-2|todo|todo-backend|xcmetrics)'",
|
||||
"build:api-reports": "yarn build:api-reports:only --tsc",
|
||||
"build:api-reports:only": "NODE_OPTIONS=--max-old-space-size=8192 backstage-repo-tools api-reports --sql-reports --allow-warnings 'packages/backend-app-api,packages/core-components,plugins/+(catalog|catalog-import|git-release-manager|jenkins|kubernetes)' -o ae-undocumented,ae-wrong-input-file-type --validate-release-tags",
|
||||
"build:api-reports:only": "LANG=en_US.UTF-8 NODE_OPTIONS=--max-old-space-size=8192 backstage-repo-tools api-reports --sql-reports --allow-warnings 'packages/backend-app-api,packages/core-components,plugins/+(catalog|catalog-import|git-release-manager|jenkins|kubernetes)' -o ae-undocumented,ae-wrong-input-file-type --validate-release-tags",
|
||||
"build:backend": "yarn workspace example-backend build",
|
||||
"build:knip-reports": "backstage-repo-tools knip-reports",
|
||||
"build:plugins-report": "node ./scripts/build-plugins-report",
|
||||
|
||||
+8
-7
@@ -1,19 +1,20 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { ExampleFetchComponent } from './ExampleFetchComponent';
|
||||
|
||||
describe('ExampleFetchComponent', () => {
|
||||
it('renders the user table', async () => {
|
||||
render(<ExampleFetchComponent />);
|
||||
const { getAllByText, getByAltText, getByText, findByRole } =
|
||||
await renderInTestApp(<ExampleFetchComponent />);
|
||||
|
||||
// Wait for the table to render
|
||||
const table = await screen.findByRole('table');
|
||||
const nationality = screen.getAllByText('GB');
|
||||
const table = await findByRole('table');
|
||||
const nationality = getAllByText('GB');
|
||||
// Assert that the table contains the expected user data
|
||||
expect(table).toBeInTheDocument();
|
||||
expect(screen.getByAltText('Carolyn')).toBeInTheDocument();
|
||||
expect(screen.getByText('Carolyn Moore')).toBeInTheDocument();
|
||||
expect(screen.getByText('carolyn.moore@example.com')).toBeInTheDocument();
|
||||
expect(getByAltText('Carolyn')).toBeInTheDocument();
|
||||
expect(getByText('Carolyn Moore')).toBeInTheDocument();
|
||||
expect(getByText('carolyn.moore@example.com')).toBeInTheDocument();
|
||||
expect(nationality[0]).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,16 @@ export const coreComponentsTranslationRef: TranslationRef<
|
||||
'core-components',
|
||||
{
|
||||
readonly 'table.filter.title': 'Filters';
|
||||
readonly 'table.filter.placeholder': 'All results';
|
||||
readonly 'table.filter.clearAll': 'Clear all';
|
||||
readonly 'table.body.emptyDataSourceMessage': 'No records to display';
|
||||
readonly 'table.toolbar.search': 'Filter';
|
||||
readonly 'table.pagination.firstTooltip': 'First Page';
|
||||
readonly 'table.pagination.labelDisplayedRows': '{from}-{to} of {count}';
|
||||
readonly 'table.pagination.labelRowsSelect': 'rows';
|
||||
readonly 'table.pagination.lastTooltip': 'Last Page';
|
||||
readonly 'table.pagination.nextTooltip': 'Next Page';
|
||||
readonly 'table.pagination.previousTooltip': 'Previous Page';
|
||||
readonly 'signIn.title': 'Sign In';
|
||||
readonly 'signIn.loginFailed': 'Login failed';
|
||||
readonly 'signIn.customProvider.title': 'Custom User';
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
TranslationFunction,
|
||||
useTranslationRef,
|
||||
} from '@backstage/core-plugin-api/alpha';
|
||||
import MTable, {
|
||||
Column,
|
||||
Icons,
|
||||
@@ -58,6 +62,7 @@ import React, {
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { SelectProps } from '../Select/Select';
|
||||
import { Filter, Filters, SelectedFilters, Without } from './Filters';
|
||||
import { TableLoadingBody } from './TableLoadingBody';
|
||||
@@ -296,6 +301,7 @@ export function TableToolbar(toolbarProps: {
|
||||
selectedFiltersLength,
|
||||
toggleFilters,
|
||||
} = toolbarProps;
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
const filtersClasses = useFilterStyles();
|
||||
const onSearchChanged = useCallback(
|
||||
(searchText: string) => {
|
||||
@@ -313,7 +319,7 @@ export function TableToolbar(toolbarProps: {
|
||||
<FilterList />
|
||||
</IconButton>
|
||||
<Typography className={filtersClasses.title}>
|
||||
Filters ({selectedFiltersLength})
|
||||
{t('table.filter.title')} ({selectedFiltersLength})
|
||||
</Typography>
|
||||
</Box>
|
||||
<StyledMTableToolbar
|
||||
@@ -354,6 +360,7 @@ export function Table<T extends object = {}>(props: TableProps<T>) {
|
||||
style,
|
||||
...restProps
|
||||
} = props;
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
const tableClasses = useTableStyles();
|
||||
|
||||
const theme = useTheme();
|
||||
@@ -457,7 +464,7 @@ export function Table<T extends object = {}>(props: TableProps<T>) {
|
||||
<Box className={tableClasses.root}>
|
||||
{filtersOpen && data && typeof data !== 'function' && filters?.length && (
|
||||
<Filters
|
||||
filters={constructFilters(filters, data as any[], columns)}
|
||||
filters={constructFilters(filters, data as any[], columns, t)}
|
||||
selectedFilters={selectedFilters}
|
||||
onChangeFilters={setSelectedFilters}
|
||||
/>
|
||||
@@ -487,8 +494,25 @@ export function Table<T extends object = {}>(props: TableProps<T>) {
|
||||
data={tableData}
|
||||
style={{ width: '100%', ...style }}
|
||||
localization={{
|
||||
toolbar: { searchPlaceholder: 'Search', searchTooltip: 'Search' },
|
||||
...localization,
|
||||
body: {
|
||||
emptyDataSourceMessage: t('table.body.emptyDataSourceMessage'),
|
||||
...localization?.body,
|
||||
},
|
||||
pagination: {
|
||||
firstTooltip: t('table.pagination.firstTooltip'),
|
||||
labelDisplayedRows: t('table.pagination.labelDisplayedRows'),
|
||||
labelRowsSelect: t('table.pagination.labelRowsSelect'),
|
||||
lastTooltip: t('table.pagination.lastTooltip'),
|
||||
nextTooltip: t('table.pagination.nextTooltip'),
|
||||
previousTooltip: t('table.pagination.previousTooltip'),
|
||||
...localization?.pagination,
|
||||
},
|
||||
toolbar: {
|
||||
searchPlaceholder: t('table.toolbar.search'),
|
||||
searchTooltip: t('table.toolbar.search'),
|
||||
...localization?.toolbar,
|
||||
},
|
||||
}}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -532,6 +556,7 @@ function constructFilters<T extends object>(
|
||||
filterConfig: TableFilter[],
|
||||
dataValue: any[] | undefined,
|
||||
columns: TableColumn<T>[],
|
||||
t: TranslationFunction<typeof coreComponentsTranslationRef.T>,
|
||||
): Filter[] {
|
||||
const extractDistinctValues = (field: string | keyof T): Set<any> => {
|
||||
const distinctValues = new Set<any>();
|
||||
@@ -563,7 +588,7 @@ function constructFilters<T extends object>(
|
||||
filter: TableFilter,
|
||||
): Without<SelectProps, 'onChange'> => {
|
||||
return {
|
||||
placeholder: 'All results',
|
||||
placeholder: t('table.filter.placeholder'),
|
||||
label: filter.column,
|
||||
multiple: filter.type === 'multiple-select',
|
||||
items: [...extractDistinctValues(filter.column)].sort().map(value => ({
|
||||
|
||||
@@ -88,6 +88,21 @@ export const coreComponentsTranslationRef = createTranslationRef({
|
||||
filter: {
|
||||
title: 'Filters',
|
||||
clearAll: 'Clear all',
|
||||
placeholder: 'All results',
|
||||
},
|
||||
body: {
|
||||
emptyDataSourceMessage: 'No records to display',
|
||||
},
|
||||
pagination: {
|
||||
firstTooltip: 'First Page',
|
||||
labelDisplayedRows: '{from}-{to} of {count}',
|
||||
labelRowsSelect: 'rows',
|
||||
lastTooltip: 'Last Page',
|
||||
nextTooltip: 'Next Page',
|
||||
previousTooltip: 'Previous Page',
|
||||
},
|
||||
toolbar: {
|
||||
search: 'Filter',
|
||||
},
|
||||
},
|
||||
alertDisplay: {
|
||||
|
||||
@@ -42,6 +42,7 @@ import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react';
|
||||
import { TemplateParameterSchema } from '@backstage/plugin-scaffolder-react';
|
||||
import { TemplatePresentationV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { TranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { UiSchema } from '@rjsf/utils';
|
||||
import { WidgetProps } from '@rjsf/utils';
|
||||
import { z } from 'zod';
|
||||
@@ -329,6 +330,29 @@ export type ScaffolderReactComponentsNameToClassKey = {
|
||||
// @alpha (undocumented)
|
||||
export type ScaffolderReactTemplateCategoryPickerClassKey = 'root' | 'label';
|
||||
|
||||
// @alpha (undocumented)
|
||||
export const scaffolderReactTranslationRef: TranslationRef<
|
||||
'scaffolder-react',
|
||||
{
|
||||
readonly 'workflow.noDescription': 'No description';
|
||||
readonly 'passwordWidget.content': 'This widget is insecure. Please use [`ui:field: Secret`](https://backstage.io/docs/features/software-templates/writing-templates/#using-secrets) instead of `ui:widget: password`';
|
||||
readonly 'scaffolderPageContextMenu.createLabel': 'Create';
|
||||
readonly 'scaffolderPageContextMenu.moreLabel': 'more';
|
||||
readonly 'scaffolderPageContextMenu.editorLabel': 'Manage Templates';
|
||||
readonly 'scaffolderPageContextMenu.actionsLabel': 'Installed Actions';
|
||||
readonly 'scaffolderPageContextMenu.tasksLabel': 'Task List';
|
||||
readonly 'stepper.backButtonText': 'Back';
|
||||
readonly 'stepper.createButtonText': 'Create';
|
||||
readonly 'stepper.reviewButtonText': 'Review';
|
||||
readonly 'stepper.stepIndexLabel': 'Step {{index, number}}';
|
||||
readonly 'stepper.nextButtonText': 'Next';
|
||||
readonly 'templateCategoryPicker.title': 'Categories';
|
||||
readonly 'templateCard.noDescription': 'No description';
|
||||
readonly 'templateCard.chooseButtonText': 'Choose';
|
||||
readonly 'templateOutputs.title': 'Text Output';
|
||||
}
|
||||
>;
|
||||
|
||||
// @alpha
|
||||
export const SecretWidget: (
|
||||
props: Pick<
|
||||
|
||||
@@ -15,3 +15,5 @@
|
||||
*/
|
||||
|
||||
export * from './next';
|
||||
|
||||
export { scaffolderReactTranslationRef } from './translation';
|
||||
|
||||
@@ -14,15 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { WidgetProps } from '@rjsf/utils';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import React from 'react';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import { MarkdownContent } from '@backstage/core-components';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import { WidgetProps } from '@rjsf/utils';
|
||||
import React from 'react';
|
||||
|
||||
import { scaffolderReactTranslationRef } from '../../../translation';
|
||||
|
||||
export const PasswordWidget = (
|
||||
props: Pick<WidgetProps, 'onChange' | 'schema' | 'value'>,
|
||||
) => {
|
||||
const { t } = useTranslationRef(scaffolderReactTranslationRef);
|
||||
|
||||
const {
|
||||
value,
|
||||
onChange,
|
||||
@@ -42,10 +47,7 @@ export const PasswordWidget = (
|
||||
autoComplete="off"
|
||||
/>
|
||||
<FormHelperText error>
|
||||
<MarkdownContent
|
||||
content="This widget is insecure. Please use [`ui:field: Secret`](https://backstage.io/docs/features/software-templates/writing-templates/#using-secrets) instead of
|
||||
`ui:widget: password`"
|
||||
/>
|
||||
<MarkdownContent content={t('passwordWidget.content')} />
|
||||
</FormHelperText>
|
||||
</>
|
||||
);
|
||||
|
||||
+18
-6
@@ -14,14 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import MenuList from '@material-ui/core/MenuList';
|
||||
import Popover from '@material-ui/core/Popover';
|
||||
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
|
||||
import Description from '@material-ui/icons/Description';
|
||||
import Edit from '@material-ui/icons/Edit';
|
||||
import List from '@material-ui/icons/List';
|
||||
@@ -31,6 +32,8 @@ import { usePermission } from '@backstage/plugin-permission-react';
|
||||
import { taskReadPermission } from '@backstage/plugin-scaffolder-common/alpha';
|
||||
import { templateManagementPermission } from '@backstage/plugin-scaffolder-common/alpha';
|
||||
|
||||
import { scaffolderReactTranslationRef } from '../../../translation';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
button: {
|
||||
color: theme.page.fontColor,
|
||||
@@ -53,6 +56,7 @@ export type ScaffolderPageContextMenuProps = {
|
||||
export function ScaffolderPageContextMenu(
|
||||
props: ScaffolderPageContextMenuProps,
|
||||
) {
|
||||
const { t } = useTranslationRef(scaffolderReactTranslationRef);
|
||||
const { onEditorClicked, onActionsClicked, onTasksClicked, onCreateClicked } =
|
||||
props;
|
||||
const classes = useStyles();
|
||||
@@ -87,7 +91,7 @@ export function ScaffolderPageContextMenu(
|
||||
<>
|
||||
<IconButton
|
||||
id="long-menu"
|
||||
aria-label="more"
|
||||
aria-label={t('scaffolderPageContextMenu.moreLabel')}
|
||||
aria-controls="long-menu"
|
||||
aria-expanded={!!anchorEl}
|
||||
aria-haspopup="true"
|
||||
@@ -113,7 +117,9 @@ export function ScaffolderPageContextMenu(
|
||||
<ListItemIcon>
|
||||
<CreateComponentIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Create" />
|
||||
<ListItemText
|
||||
primary={t('scaffolderPageContextMenu.createLabel')}
|
||||
/>
|
||||
</MenuItem>
|
||||
)}
|
||||
{onEditorClicked && canManageTemplates && (
|
||||
@@ -121,7 +127,9 @@ export function ScaffolderPageContextMenu(
|
||||
<ListItemIcon>
|
||||
<Edit fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Manage Templates" />
|
||||
<ListItemText
|
||||
primary={t('scaffolderPageContextMenu.editorLabel')}
|
||||
/>
|
||||
</MenuItem>
|
||||
)}
|
||||
{onActionsClicked && (
|
||||
@@ -129,7 +137,9 @@ export function ScaffolderPageContextMenu(
|
||||
<ListItemIcon>
|
||||
<Description fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Installed Actions" />
|
||||
<ListItemText
|
||||
primary={t('scaffolderPageContextMenu.actionsLabel')}
|
||||
/>
|
||||
</MenuItem>
|
||||
)}
|
||||
{onTasksClicked && canReadTasks && (
|
||||
@@ -137,7 +147,9 @@ export function ScaffolderPageContextMenu(
|
||||
<ListItemIcon>
|
||||
<List fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Task List" />
|
||||
<ListItemText
|
||||
primary={t('scaffolderPageContextMenu.tasksLabel')}
|
||||
/>
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuList>
|
||||
|
||||
@@ -14,44 +14,47 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useAnalytics, useApiHolder } from '@backstage/core-plugin-api';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
FieldExtensionOptions,
|
||||
FormProps,
|
||||
LayoutOptions,
|
||||
ReviewStepProps,
|
||||
TemplateParameterSchema,
|
||||
} from '@backstage/plugin-scaffolder-react';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import MuiStepper from '@material-ui/core/Stepper';
|
||||
import MuiStep from '@material-ui/core/Step';
|
||||
import MuiStepLabel from '@material-ui/core/StepLabel';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import LinearProgress from '@material-ui/core/LinearProgress';
|
||||
import MuiStep from '@material-ui/core/Step';
|
||||
import MuiStepLabel from '@material-ui/core/StepLabel';
|
||||
import MuiStepper from '@material-ui/core/Stepper';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { type IChangeEvent } from '@rjsf/core';
|
||||
import { ErrorSchema } from '@rjsf/utils';
|
||||
import { customizeValidator } from '@rjsf/validator-ajv8';
|
||||
import ajvErrors from 'ajv-errors';
|
||||
import { merge } from 'lodash';
|
||||
import React, {
|
||||
ComponentType,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
type ReactNode,
|
||||
ComponentType,
|
||||
} from 'react';
|
||||
|
||||
import { scaffolderReactTranslationRef } from '../../../translation';
|
||||
import { useFormDataFromQuery, useTemplateSchema } from '../../hooks';
|
||||
import { useTransformSchemaToProps } from '../../hooks/useTransformSchemaToProps';
|
||||
import { Form } from '../Form';
|
||||
import { PasswordWidget } from '../PasswordWidget/PasswordWidget';
|
||||
import { ReviewState, type ReviewStateProps } from '../ReviewState';
|
||||
import {
|
||||
createAsyncValidators,
|
||||
type FormValidation,
|
||||
} from './createAsyncValidators';
|
||||
import { ReviewState, type ReviewStateProps } from '../ReviewState';
|
||||
import { useTemplateSchema, useFormDataFromQuery } from '../../hooks';
|
||||
import { customizeValidator } from '@rjsf/validator-ajv8';
|
||||
import { useTransformSchemaToProps } from '../../hooks/useTransformSchemaToProps';
|
||||
import { hasErrors } from './utils';
|
||||
import * as FieldOverrides from './FieldOverrides';
|
||||
import { Form } from '../Form';
|
||||
import {
|
||||
TemplateParameterSchema,
|
||||
LayoutOptions,
|
||||
FieldExtensionOptions,
|
||||
FormProps,
|
||||
} from '@backstage/plugin-scaffolder-react';
|
||||
import { ReviewStepProps } from '@backstage/plugin-scaffolder-react';
|
||||
import { ErrorListTemplate } from './ErrorListTemplate';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { PasswordWidget } from '../PasswordWidget/PasswordWidget';
|
||||
import ajvErrors from 'ajv-errors';
|
||||
import { merge } from 'lodash';
|
||||
import * as FieldOverrides from './FieldOverrides';
|
||||
import { hasErrors } from './utils';
|
||||
|
||||
const validator = customizeValidator();
|
||||
ajvErrors(validator.ajv);
|
||||
@@ -111,13 +114,14 @@ export type StepperProps = {
|
||||
* @alpha
|
||||
*/
|
||||
export const Stepper = (stepperProps: StepperProps) => {
|
||||
const { t } = useTranslationRef(scaffolderReactTranslationRef);
|
||||
const { layouts = [], components = {}, ...props } = stepperProps;
|
||||
const {
|
||||
ReviewStateComponent = ReviewState,
|
||||
ReviewStepComponent,
|
||||
backButtonText = 'Back',
|
||||
createButtonText = 'Create',
|
||||
reviewButtonText = 'Review',
|
||||
backButtonText = t('stepper.backButtonText'),
|
||||
createButtonText = t('stepper.createButtonText'),
|
||||
reviewButtonText = t('stepper.reviewButtonText'),
|
||||
} = components;
|
||||
const analytics = useAnalytics();
|
||||
const { presentation, steps } = useTemplateSchema(props.manifest);
|
||||
@@ -235,7 +239,7 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
return (
|
||||
<MuiStep key={index}>
|
||||
<MuiStepLabel
|
||||
aria-label={`Step ${index + 1}`}
|
||||
aria-label={t('stepper.stepIndexLabel', { index: index + 1 })}
|
||||
style={{ cursor: isAllowedLabelClick ? 'pointer' : 'default' }}
|
||||
onClick={() => {
|
||||
if (isAllowedLabelClick) setActiveStep(index);
|
||||
@@ -286,7 +290,9 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
type="submit"
|
||||
disabled={isValidating}
|
||||
>
|
||||
{activeStep === steps.length - 1 ? reviewLabel : 'Next'}
|
||||
{activeStep === steps.length - 1
|
||||
? reviewLabel
|
||||
: t('stepper.nextButtonText')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
@@ -15,11 +15,14 @@
|
||||
*/
|
||||
|
||||
import { UserIcon } from '@backstage/core-components';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
import { EntityRefLinks } from '@backstage/plugin-catalog-react';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import { makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
|
||||
import { scaffolderReactTranslationRef } from '../../../translation';
|
||||
|
||||
const useStyles = makeStyles<Theme>(theme => ({
|
||||
footer: {
|
||||
display: 'flex',
|
||||
@@ -50,6 +53,7 @@ export const TemplateCardActions = ({
|
||||
handleChoose,
|
||||
ownedByRelations,
|
||||
}: TemplateCardActionsProps) => {
|
||||
const { t } = useTranslationRef(scaffolderReactTranslationRef);
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
@@ -78,7 +82,7 @@ export const TemplateCardActions = ({
|
||||
data-testid="template-card-actions--create"
|
||||
onClick={handleChoose}
|
||||
>
|
||||
Choose
|
||||
{t('templateCard.chooseButtonText')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -15,12 +15,15 @@
|
||||
*/
|
||||
|
||||
import { MarkdownContent } from '@backstage/core-components';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
import type { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
|
||||
import { scaffolderReactTranslationRef } from '../../../translation';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
box: {
|
||||
overflow: 'hidden',
|
||||
@@ -45,13 +48,16 @@ export interface TemplateCardContentProps {
|
||||
template: TemplateEntityV1beta3;
|
||||
}
|
||||
export const TemplateCardContent = ({ template }: TemplateCardContentProps) => {
|
||||
const { t } = useTranslationRef(scaffolderReactTranslationRef);
|
||||
const styles = useStyles();
|
||||
return (
|
||||
<Grid item xs={12} data-testid="template-card-content-grid">
|
||||
<Box className={styles.box} data-testid="template-card-content-container">
|
||||
<MarkdownContent
|
||||
className={styles.markdown}
|
||||
content={template.metadata.description ?? 'No description'}
|
||||
content={
|
||||
template.metadata.description ?? t('templateCard.noDescription')
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
+9
-5
@@ -14,9 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
import { Progress } from '@backstage/core-components';
|
||||
import { alertApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
import { useEntityTypeFilter } from '@backstage/plugin-catalog-react';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
@@ -27,8 +28,10 @@ import CheckBoxIcon from '@material-ui/icons/CheckBox';
|
||||
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
import { useEntityTypeFilter } from '@backstage/plugin-catalog-react';
|
||||
import { alertApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
import { scaffolderReactTranslationRef } from '../../../translation';
|
||||
|
||||
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
|
||||
const checkedIcon = <CheckBoxIcon fontSize="small" />;
|
||||
@@ -50,6 +53,7 @@ const useStyles = makeStyles(
|
||||
* @alpha
|
||||
*/
|
||||
export const TemplateCategoryPicker = () => {
|
||||
const { t } = useTranslationRef(scaffolderReactTranslationRef);
|
||||
const classes = useStyles();
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const { error, loading, availableTypes, selectedTypes, setSelectedTypes } =
|
||||
@@ -75,7 +79,7 @@ export const TemplateCategoryPicker = () => {
|
||||
component="label"
|
||||
htmlFor="categories-picker"
|
||||
>
|
||||
Categories
|
||||
{t('templateCategoryPicker.title')}
|
||||
</Typography>
|
||||
<Autocomplete<string, true>
|
||||
PopperComponent={popperProps => (
|
||||
|
||||
@@ -13,17 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { Link, Progress } from '@backstage/core-components';
|
||||
import { errorApiRef, IconComponent, useApi } from '@backstage/core-plugin-api';
|
||||
import { useEntityList } from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
isTemplateEntityV1beta3,
|
||||
TemplateEntityV1beta3,
|
||||
} from '@backstage/plugin-scaffolder-common';
|
||||
import { Progress, Link } from '@backstage/core-components';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { errorApiRef, IconComponent, useApi } from '@backstage/core-plugin-api';
|
||||
import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { TemplateGroup } from '../TemplateGroup/TemplateGroup';
|
||||
|
||||
/**
|
||||
|
||||
+6
-2
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { InfoCard, MarkdownContent } from '@backstage/core-components';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
ScaffolderOutputText,
|
||||
ScaffolderTaskOutput,
|
||||
@@ -21,6 +22,8 @@ import {
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { scaffolderReactTranslationRef } from '../../../translation';
|
||||
import { LinkOutputs } from './LinkOutputs';
|
||||
import { TextOutputs } from './TextOutputs';
|
||||
|
||||
@@ -32,6 +35,7 @@ import { TextOutputs } from './TextOutputs';
|
||||
export const DefaultTemplateOutputs = (props: {
|
||||
output?: ScaffolderTaskOutput;
|
||||
}) => {
|
||||
const { t } = useTranslationRef(scaffolderReactTranslationRef);
|
||||
const { output } = props;
|
||||
const [textOutputIndex, setTextOutputIndex] = useState<number | undefined>(
|
||||
undefined,
|
||||
@@ -40,7 +44,7 @@ export const DefaultTemplateOutputs = (props: {
|
||||
useEffect(() => {
|
||||
if (textOutputIndex === undefined && output?.text) {
|
||||
const defaultIndex = output.text.findIndex(
|
||||
(t: ScaffolderOutputText) => t.default,
|
||||
(text: ScaffolderOutputText) => text.default,
|
||||
);
|
||||
setTextOutputIndex(defaultIndex >= 0 ? defaultIndex : 0);
|
||||
}
|
||||
@@ -87,7 +91,7 @@ export const DefaultTemplateOutputs = (props: {
|
||||
{textOutput ? (
|
||||
<Box paddingBottom={2} data-testid="text-output-box">
|
||||
<InfoCard
|
||||
title={textOutput.title ?? 'Text Output'}
|
||||
title={textOutput.title ?? t('templateOutputs.title')}
|
||||
noPadding
|
||||
titleTypographyProps={{ component: 'h2' }}
|
||||
>
|
||||
|
||||
@@ -14,23 +14,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import {
|
||||
Content,
|
||||
InfoCard,
|
||||
MarkdownContent,
|
||||
Progress,
|
||||
} from '@backstage/core-components';
|
||||
import { stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { errorApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api';
|
||||
import { useTemplateParameterSchema } from '../../hooks/useTemplateParameterSchema';
|
||||
import { Stepper, type StepperProps } from '../Stepper/Stepper';
|
||||
import { SecretsContextProvider } from '../../../secrets/SecretsContext';
|
||||
import { useFilteredSchemaProperties } from '../../hooks/useFilteredSchemaProperties';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
import { ReviewStepProps } from '@backstage/plugin-scaffolder-react';
|
||||
import { useTemplateTimeSavedMinutes } from '../../hooks/useTemplateTimeSaved';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
|
||||
import { SecretsContextProvider } from '../../../secrets/SecretsContext';
|
||||
import { scaffolderReactTranslationRef } from '../../../translation';
|
||||
import { useFilteredSchemaProperties } from '../../hooks/useFilteredSchemaProperties';
|
||||
import { useTemplateParameterSchema } from '../../hooks/useTemplateParameterSchema';
|
||||
import { useTemplateTimeSavedMinutes } from '../../hooks/useTemplateTimeSaved';
|
||||
import { Stepper, type StepperProps } from '../Stepper/Stepper';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
markdown: {
|
||||
@@ -70,6 +73,7 @@ export type WorkflowProps = {
|
||||
* @alpha
|
||||
*/
|
||||
export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
|
||||
const { t } = useTranslationRef(scaffolderReactTranslationRef);
|
||||
const { title, description, namespace, templateName, onCreate, ...props } =
|
||||
workflowProps;
|
||||
|
||||
@@ -123,7 +127,9 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
|
||||
className={styles.markdown}
|
||||
linkTarget="_blank"
|
||||
content={
|
||||
description ?? sortedManifest.description ?? 'No description'
|
||||
description ??
|
||||
sortedManifest.description ??
|
||||
t('workflow.noDescription')
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
/** @alpha */
|
||||
export const scaffolderReactTranslationRef = createTranslationRef({
|
||||
id: 'scaffolder-react',
|
||||
messages: {
|
||||
passwordWidget: {
|
||||
content:
|
||||
'This widget is insecure. Please use [`ui:field: Secret`](https://backstage.io/docs/features/software-templates/writing-templates/#using-secrets) instead of `ui:widget: password`',
|
||||
},
|
||||
scaffolderPageContextMenu: {
|
||||
moreLabel: 'more',
|
||||
createLabel: 'Create',
|
||||
editorLabel: 'Manage Templates',
|
||||
actionsLabel: 'Installed Actions',
|
||||
tasksLabel: 'Task List',
|
||||
},
|
||||
stepper: {
|
||||
backButtonText: 'Back',
|
||||
createButtonText: 'Create',
|
||||
reviewButtonText: 'Review',
|
||||
stepIndexLabel: 'Step {{index, number}}',
|
||||
nextButtonText: 'Next',
|
||||
},
|
||||
templateCategoryPicker: {
|
||||
title: 'Categories',
|
||||
},
|
||||
templateCard: {
|
||||
noDescription: 'No description',
|
||||
chooseButtonText: 'Choose',
|
||||
},
|
||||
templateOutputs: {
|
||||
title: 'Text Output',
|
||||
},
|
||||
workflow: {
|
||||
noDescription: 'No description',
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user