From 48aab132f6212a536284fd5f0588e8368e8b5c7f Mon Sep 17 00:00:00 2001 From: JounQin Date: Wed, 11 Dec 2024 18:17:29 +0800 Subject: [PATCH] feat: add i18n support for scaffolder-react plugin Signed-off-by: JounQin --- .changeset/old-avocados-grow.md | 6 ++ package.json | 2 +- .../ExampleFetchComponent.test.tsx.hbs | 15 ++--- packages/core-components/report-alpha.api.md | 9 +++ .../src/components/Table/Table.tsx | 33 ++++++++-- packages/core-components/src/translation.ts | 15 +++++ plugins/scaffolder-react/report-alpha.api.md | 24 ++++++++ plugins/scaffolder-react/src/alpha.ts | 2 + .../PasswordWidget/PasswordWidget.tsx | 18 +++--- .../ScaffolderPageContextMenu.tsx | 24 ++++++-- .../src/next/components/Stepper/Stepper.tsx | 60 ++++++++++--------- .../TemplateCard/TemplateCardActions.tsx | 6 +- .../TemplateCard/TemplateCardContent.tsx | 10 +++- .../TemplateCategoryPicker.tsx | 14 +++-- .../TemplateGroups/TemplateGroups.tsx | 10 ++-- .../DefaultTemplateOutputs.tsx | 8 ++- .../src/next/components/Workflow/Workflow.tsx | 24 +++++--- plugins/scaffolder-react/src/translation.ts | 54 +++++++++++++++++ 18 files changed, 257 insertions(+), 77 deletions(-) create mode 100644 .changeset/old-avocados-grow.md create mode 100644 plugins/scaffolder-react/src/translation.ts diff --git a/.changeset/old-avocados-grow.md b/.changeset/old-avocados-grow.md new file mode 100644 index 0000000000..9f32c28922 --- /dev/null +++ b/.changeset/old-avocados-grow.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-components': patch +'@backstage/plugin-scaffolder-react': patch +--- + +Add i18n support for scaffolder-react plugin diff --git a/package.json b/package.json index 4c92b778ce..8aba6c2ad4 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/cli/templates/frontend-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs b/packages/cli/templates/frontend-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs index 1e746ff39a..be43c05ba2 100644 --- a/packages/cli/templates/frontend-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs +++ b/packages/cli/templates/frontend-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs @@ -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(); + const { getAllByText, getByAltText, getByText, findByRole } = + await renderInTestApp(); // 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(); }); }); diff --git a/packages/core-components/report-alpha.api.md b/packages/core-components/report-alpha.api.md index 1f4b999232..158f9fe14a 100644 --- a/packages/core-components/report-alpha.api.md +++ b/packages/core-components/report-alpha.api.md @@ -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'; diff --git a/packages/core-components/src/components/Table/Table.tsx b/packages/core-components/src/components/Table/Table.tsx index 1a9e716abc..f5b0716589 100644 --- a/packages/core-components/src/components/Table/Table.tsx +++ b/packages/core-components/src/components/Table/Table.tsx @@ -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: { - Filters ({selectedFiltersLength}) + {t('table.filter.title')} ({selectedFiltersLength}) (props: TableProps) { style, ...restProps } = props; + const { t } = useTranslationRef(coreComponentsTranslationRef); const tableClasses = useTableStyles(); const theme = useTheme(); @@ -457,7 +464,7 @@ export function Table(props: TableProps) { {filtersOpen && data && typeof data !== 'function' && filters?.length && ( @@ -487,8 +494,25 @@ export function Table(props: TableProps) { 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( filterConfig: TableFilter[], dataValue: any[] | undefined, columns: TableColumn[], + t: TranslationFunction, ): Filter[] { const extractDistinctValues = (field: string | keyof T): Set => { const distinctValues = new Set(); @@ -563,7 +588,7 @@ function constructFilters( filter: TableFilter, ): Without => { return { - placeholder: 'All results', + placeholder: t('table.filter.placeholder'), label: filter.column, multiple: filter.type === 'multiple-select', items: [...extractDistinctValues(filter.column)].sort().map(value => ({ diff --git a/packages/core-components/src/translation.ts b/packages/core-components/src/translation.ts index 91474e73b7..13bd62bab8 100644 --- a/packages/core-components/src/translation.ts +++ b/packages/core-components/src/translation.ts @@ -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: { diff --git a/plugins/scaffolder-react/report-alpha.api.md b/plugins/scaffolder-react/report-alpha.api.md index 9af439f15a..c9401fee19 100644 --- a/plugins/scaffolder-react/report-alpha.api.md +++ b/plugins/scaffolder-react/report-alpha.api.md @@ -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< diff --git a/plugins/scaffolder-react/src/alpha.ts b/plugins/scaffolder-react/src/alpha.ts index 36c3fcf264..e18c758b3d 100644 --- a/plugins/scaffolder-react/src/alpha.ts +++ b/plugins/scaffolder-react/src/alpha.ts @@ -15,3 +15,5 @@ */ export * from './next'; + +export { scaffolderReactTranslationRef } from './translation'; diff --git a/plugins/scaffolder-react/src/next/components/PasswordWidget/PasswordWidget.tsx b/plugins/scaffolder-react/src/next/components/PasswordWidget/PasswordWidget.tsx index 1807df8ae2..6ccd353e9e 100644 --- a/plugins/scaffolder-react/src/next/components/PasswordWidget/PasswordWidget.tsx +++ b/plugins/scaffolder-react/src/next/components/PasswordWidget/PasswordWidget.tsx @@ -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, ) => { + const { t } = useTranslationRef(scaffolderReactTranslationRef); + const { value, onChange, @@ -42,10 +47,7 @@ export const PasswordWidget = ( autoComplete="off" /> - + ); diff --git a/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx b/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx index 38d83ae6fd..7cc56c2d5f 100644 --- a/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx +++ b/plugins/scaffolder-react/src/next/components/ScaffolderPageContextMenu/ScaffolderPageContextMenu.tsx @@ -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( <> - + )} {onEditorClicked && canManageTemplates && ( @@ -121,7 +127,9 @@ export function ScaffolderPageContextMenu( - + )} {onActionsClicked && ( @@ -129,7 +137,9 @@ export function ScaffolderPageContextMenu( - + )} {onTasksClicked && canReadTasks && ( @@ -137,7 +147,9 @@ export function ScaffolderPageContextMenu( - + )} diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 0b4cbbc94a..9ca37e284a 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -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 ( { 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')} diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCardActions.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCardActions.tsx index e8a84487df..69534acd4d 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCardActions.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCardActions.tsx @@ -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 => ({ 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')} ) : null} diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCardContent.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCardContent.tsx index fad5c85474..d46b5e032e 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCardContent.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCardContent.tsx @@ -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 ( diff --git a/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx b/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx index f2505ec148..9bfe3722e6 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx @@ -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 = ; const checkedIcon = ; @@ -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')} PopperComponent={popperProps => ( diff --git a/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx b/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx index a180ff0e8c..aeeb268d53 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx @@ -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'; /** diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx index 40abad376b..0ba2906054 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx @@ -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( 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 ? ( diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx index 31f5ac2881..0886e233ff 100644 --- a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx +++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx @@ -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') } /> } diff --git a/plugins/scaffolder-react/src/translation.ts b/plugins/scaffolder-react/src/translation.ts new file mode 100644 index 0000000000..f490b52064 --- /dev/null +++ b/plugins/scaffolder-react/src/translation.ts @@ -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', + }, + }, +});