From 2ae63cd7a700aa45157cb69cee7af4de19d1c95f Mon Sep 17 00:00:00 2001 From: mario ma Date: Tue, 30 Jul 2024 11:23:25 +0800 Subject: [PATCH] feat: add i18n for scaffolder Signed-off-by: mario ma --- .changeset/many-years-lie.md | 5 + plugins/scaffolder/src/alpha.tsx | 2 + .../components/ActionsPage/ActionsPage.tsx | 34 ++- .../ListTasksPage/ListTasksPage.tsx | 26 +- .../ListTasksPage/OwnerListPicker.tsx | 18 +- .../components/OngoingTask/ContextMenu.tsx | 21 +- .../components/OngoingTask/OngoingTask.tsx | 21 +- .../TemplateTypePicker.test.tsx | 6 +- .../TemplateTypePicker/TemplateTypePicker.tsx | 5 +- .../EntityNamePicker/EntityNamePicker.tsx | 8 +- .../fields/EntityPicker/EntityPicker.tsx | 8 +- .../EntityTagsPicker/EntityTagsPicker.tsx | 10 +- .../MyGroupsPicker/MyGroupsPicker.test.tsx | 12 +- .../fields/MyGroupsPicker/MyGroupsPicker.tsx | 8 +- .../OwnedEntityPicker/OwnedEntityPicker.tsx | 8 +- .../fields/OwnerPicker/OwnerPicker.tsx | 8 +- .../RepoUrlPicker/AzureRepoPicker.test.tsx | 13 +- .../fields/RepoUrlPicker/AzureRepoPicker.tsx | 65 ++-- .../BitbucketRepoPicker.test.tsx | 34 +-- .../RepoUrlPicker/BitbucketRepoPicker.tsx | 24 +- .../RepoUrlPicker/GerritRepoPicker.test.tsx | 11 +- .../fields/RepoUrlPicker/GerritRepoPicker.tsx | 13 +- .../RepoUrlPicker/GiteaRepoPicker.test.tsx | 7 +- .../fields/RepoUrlPicker/GiteaRepoPicker.tsx | 41 +-- .../RepoUrlPicker/GithubRepoPicker.test.tsx | 13 +- .../fields/RepoUrlPicker/GithubRepoPicker.tsx | 34 ++- .../RepoUrlPicker/GitlabRepoPicker.test.tsx | 13 +- .../fields/RepoUrlPicker/GitlabRepoPicker.tsx | 41 +-- .../RepoUrlPicker/RepoUrlPickerHost.tsx | 7 +- .../RepoUrlPickerRepoName.test.tsx | 11 +- .../RepoUrlPicker/RepoUrlPickerRepoName.tsx | 15 +- .../CustomFieldExplorer.tsx | 19 +- .../DryRunResults/DryRunResults.tsx | 5 +- .../DryRunResults/DryRunResultsList.tsx | 17 +- .../DryRunResults/DryRunResultsView.tsx | 18 +- .../DryRunResults/TaskStatusStepper.tsx | 9 +- .../TemplateEditorBrowser.tsx | 19 +- .../TemplateEditorIntro.tsx | 26 +- .../TemplateEditorPage/TemplateEditorPage.tsx | 7 +- .../TemplateEditorTextArea.tsx | 15 +- .../TemplateFormPreviewer.tsx | 7 +- .../TemplateListPage/TemplateListPage.tsx | 45 +-- .../TemplateWizardPage/TemplateWizardPage.tsx | 9 +- .../TemplateWizardPageContextMenu.tsx | 9 +- plugins/scaffolder/src/translation.ts | 286 ++++++++++++++++++ 45 files changed, 776 insertions(+), 257 deletions(-) create mode 100644 .changeset/many-years-lie.md create mode 100644 plugins/scaffolder/src/translation.ts diff --git a/.changeset/many-years-lie.md b/.changeset/many-years-lie.md new file mode 100644 index 0000000000..8615ddba21 --- /dev/null +++ b/.changeset/many-years-lie.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +add i18n for scaffolder diff --git a/plugins/scaffolder/src/alpha.tsx b/plugins/scaffolder/src/alpha.tsx index 3136626f47..5b7353053e 100644 --- a/plugins/scaffolder/src/alpha.tsx +++ b/plugins/scaffolder/src/alpha.tsx @@ -106,3 +106,5 @@ export default createFrontendPlugin({ }), extensions: [scaffolderApi, scaffolderPage, scaffolderNavItem], }); + +export * from './translation'; \ No newline at end of file diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx index 7e859e272d..2f9bcba279 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx @@ -58,6 +58,8 @@ import { rootRouteRef, scaffolderListTaskRouteRef, } from '../../routes'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ code: { @@ -115,6 +117,7 @@ const ExamplesTable = (props: { examples: ActionExample[] }) => { const ActionPageContent = () => { const api = useApi(scaffolderApiRef); + const { t } = useTranslationRef(scaffolderTranslationRef); const classes = useStyles(); const { loading, value, error } = useAsync(async () => { @@ -132,8 +135,8 @@ const ActionPageContent = () => { ); @@ -141,17 +144,21 @@ const ActionPageContent = () => { const renderTable = (rows?: JSX.Element[]) => { if (!rows || rows.length < 1) { - return No schema defined; + return ( + {t('actionsPage.content.noRowsDescription')} + ); } return ( - Name - Title - Description - Type + {t('actionsPage.content.tableCell.name')} + {t('actionsPage.content.tableCell.title')} + + {t('actionsPage.content.tableCell.description')} + + {t('actionsPage.content.tableCell.type')} {rows} @@ -295,7 +302,7 @@ const ActionPageContent = () => { {action.schema?.input && ( - Input + {t('actionsPage.action.input')} {renderTable( formatRows(`${action.id}.input`, action?.schema?.input), @@ -306,7 +313,7 @@ const ActionPageContent = () => { {action.schema?.output && ( - Output + {t('actionsPage.action.output')} {renderTable( formatRows(`${action.id}.output`, action?.schema?.output), @@ -317,7 +324,7 @@ const ActionPageContent = () => { }> - Examples + {t('actionsPage.action.examples')} @@ -336,6 +343,7 @@ export const ActionsPage = () => { const editorLink = useRouteRef(editRouteRef); const tasksLink = useRouteRef(scaffolderListTaskRouteRef); const createLink = useRouteRef(rootRouteRef); + const { t } = useTranslationRef(scaffolderTranslationRef); const scaffolderPageContextMenuProps = { onEditorClicked: () => navigate(editorLink()), @@ -347,9 +355,9 @@ export const ActionsPage = () => { return (
diff --git a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx index d37716a710..7bc34662ef 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx @@ -41,6 +41,8 @@ import { import { actionsRouteRef, editRouteRef, rootRouteRef } from '../../routes'; import { ScaffolderPageContextMenu } from '@backstage/plugin-scaffolder-react/alpha'; import { useNavigate } from 'react-router-dom'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; export interface MyTaskPageProps { initiallySelectedFilter?: 'owned' | 'all'; @@ -48,6 +50,7 @@ export interface MyTaskPageProps { const ListTaskPageContent = (props: MyTaskPageProps) => { const { initiallySelectedFilter = 'owned' } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const scaffolderApi = useApi(scaffolderApiRef); const rootLink = useRouteRef(rootRouteRef); @@ -76,8 +79,8 @@ const ListTaskPageContent = (props: MyTaskPageProps) => { ); @@ -94,17 +97,17 @@ const ListTaskPageContent = (props: MyTaskPageProps) => { data={value?.tasks ?? []} - title="Tasks" + title={t('listTaskPage.content.tableTitle')} columns={[ { - title: 'Task ID', + title: t('listTaskPage.content.tableCell.taskID'), field: 'id', render: row => ( {row.id} ), }, { - title: 'Template', + title: t('listTaskPage.content.tableCell.template'), field: 'spec.templateInfo.entity.metadata.title', render: row => ( { ), }, { - title: 'Created', + title: t('listTaskPage.content.tableCell.created'), field: 'createdAt', render: row => , }, { - title: 'Owner', + title: t('listTaskPage.content.tableCell.owner'), field: 'createdBy', render: row => ( ), }, { - title: 'Status', + title: t('listTaskPage.content.tableCell.status'), field: 'status', render: row => , }, @@ -141,6 +144,7 @@ export const ListTasksPage = (props: MyTaskPageProps) => { const editorLink = useRouteRef(editRouteRef); const actionsLink = useRouteRef(actionsRouteRef); const createLink = useRouteRef(rootRouteRef); + const { t } = useTranslationRef(scaffolderTranslationRef); const scaffolderPageContextMenuProps = { onEditorClicked: () => navigate(editorLink()), @@ -151,9 +155,9 @@ export const ListTasksPage = (props: MyTaskPageProps) => { return (
diff --git a/plugins/scaffolder/src/components/ListTasksPage/OwnerListPicker.tsx b/plugins/scaffolder/src/components/ListTasksPage/OwnerListPicker.tsx index d2489303d3..00efed45ba 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/OwnerListPicker.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/OwnerListPicker.tsx @@ -23,6 +23,11 @@ import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import SettingsIcon from '@material-ui/icons/Settings'; import React, { Fragment } from 'react'; +import { + TranslationFunction, + useTranslationRef, +} from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; import AllIcon from '@material-ui/icons/FontDownload'; @@ -64,19 +69,21 @@ export type ButtonGroup = { }[]; }; -function getFilterGroups(): ButtonGroup[] { +function getFilterGroups( + t: TranslationFunction, +): ButtonGroup[] { return [ { - name: 'Task Owner', + name: t('ownerListPicker.title'), items: [ { id: 'owned', - label: 'Owned', + label: t('ownerListPicker.options.owned'), icon: SettingsIcon, }, { id: 'all', - label: 'All', + label: t('ownerListPicker.options.all'), icon: AllIcon, }, ], @@ -90,8 +97,9 @@ export const OwnerListPicker = (props: { }) => { const { filter, onSelectOwner } = props; const classes = useStyles(); + const { t } = useTranslationRef(scaffolderTranslationRef); - const filterGroups = getFilterGroups(); + const filterGroups = getFilterGroups(t); return ( {filterGroups.map(group => ( diff --git a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx index f96f83fd2f..e0ab01fc67 100644 --- a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx @@ -36,6 +36,8 @@ import { taskReadPermission, taskCreatePermission, } from '@backstage/plugin-scaffolder-common/alpha'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; type ContextMenuProps = { cancelEnabled?: boolean; @@ -69,6 +71,7 @@ export const ContextMenu = (props: ContextMenuProps) => { const scaffolderApi = useApi(scaffolderApiRef); const analytics = useAnalytics(); const [anchorEl, setAnchorEl] = useState(); + const { t } = useTranslationRef(scaffolderTranslationRef); const [{ status: cancelStatus }, { execute: cancel }] = useAsync(async () => { if (taskId) { @@ -118,14 +121,24 @@ export const ContextMenu = (props: ContextMenuProps) => { - + onToggleButtonBar?.(!buttonBarVisible)}> { - + { - + diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index 459ba682d2..b956e27aca 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx @@ -42,6 +42,8 @@ import { taskReadPermission, taskCreatePermission, } from '@backstage/plugin-scaffolder-common/alpha'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -85,6 +87,7 @@ export const OngoingTask = (props: { })) ?? [], [taskStream], ); + const { t } = useTranslationRef(scaffolderTranslationRef); const [logsVisible, setLogVisibleState] = useState(false); const [buttonBarVisible, setButtonBarVisibleState] = useState(true); @@ -166,7 +169,7 @@ export const OngoingTask = (props: { const Outputs = props.TemplateOutputsComponent ?? DefaultTemplateOutputs; const templateName = - taskStream.task?.spec.templateInfo?.entity?.metadata.name; + taskStream.task?.spec.templateInfo?.entity?.metadata.name || ''; const cancelEnabled = !(taskStream.cancelled || taskStream.completed); @@ -174,14 +177,16 @@ export const OngoingTask = (props: {
- Run of {templateName} + {t('ongoingTask.title')} {templateName} } - subtitle={`Task ${taskId}`} + subtitle={t('ongoingTask.subtitle', { taskId: taskId as string })} > - Cancel + {t('ongoingTask.cancelButtonTitle')} diff --git a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx index aa6630d4aa..4c4b88e471 100644 --- a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx +++ b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx @@ -26,7 +26,7 @@ import { } from '@backstage/plugin-catalog-react'; import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; -import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils'; +import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import { GetEntityFacetsResponse } from '@backstage/catalog-client'; const entities: Entity[] = [ @@ -86,7 +86,7 @@ const apis = TestApiRegistry.from( describe('', () => { it('renders available entity types', async () => { - const rendered = await renderWithEffects( + const rendered = await renderInTestApp( ', () => { }); it('sets the selected type filters', async () => { - const rendered = await renderWithEffects( + const rendered = await renderInTestApp( ; const checkedIcon = ; @@ -41,6 +43,7 @@ export const TemplateTypePicker = () => { const alertApi = useApi(alertApiRef); const { error, loading, availableTypes, selectedTypes, setSelectedTypes } = useEntityTypeFilter(); + const { t } = useTranslationRef(scaffolderTranslationRef); if (loading) return ; @@ -61,7 +64,7 @@ export const TemplateTypePicker = () => { component="label" htmlFor="categories-picker" > - Categories + {t('templateTypePicker.title')} id="categories-picker" diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx index 9f49fc2b94..c219ae0d63 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx @@ -16,6 +16,8 @@ import React from 'react'; import { EntityNamePickerProps } from './schema'; import TextField from '@material-ui/core/TextField'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { EntityNamePickerSchema } from './schema'; @@ -23,10 +25,14 @@ export { EntityNamePickerSchema } from './schema'; * EntityName Picker */ export const EntityNamePicker = (props: EntityNamePickerProps) => { + const { t } = useTranslationRef(scaffolderTranslationRef); const { onChange, required, - schema: { title = 'Name', description = 'Unique name of the component' }, + schema: { + title = t('fields.entityNamePicker.title'), + description = t('fields.entityNamePicker.description'), + }, rawErrors, formData, uiSchema: { 'ui:autofocus': autoFocus }, diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index 9eca3e85c5..b97b776b19 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -44,6 +44,8 @@ import { EntityPickerFilterQuery, } from './schema'; import { VirtualizedListbox } from '../VirtualizedListbox'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { EntityPickerSchema } from './schema'; @@ -54,9 +56,13 @@ export { EntityPickerSchema } from './schema'; * @public */ export const EntityPicker = (props: EntityPickerProps) => { + const { t } = useTranslationRef(scaffolderTranslationRef); const { onChange, - schema: { title = 'Entity', description = 'An entity from the catalog' }, + schema: { + title = t('fields.entityPicker.title'), + description = t('fields.entityPicker.description'), + }, required, uiSchema, rawErrors, diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx index bf95a08c85..25b5d926a5 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx @@ -24,6 +24,8 @@ import FormControl from '@material-ui/core/FormControl'; import TextField from '@material-ui/core/TextField'; import Autocomplete from '@material-ui/lab/Autocomplete'; import { EntityTagsPickerProps } from './schema'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { EntityTagsPickerSchema } from './schema'; @@ -43,6 +45,7 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => { const kinds = uiSchema['ui:options']?.kinds; const showCounts = uiSchema['ui:options']?.showCounts; const helperText = uiSchema['ui:options']?.helperText; + const { t } = useTranslationRef(scaffolderTranslationRef); const { loading, value: existingTags } = useAsync(async () => { const facet = 'metadata.tags'; @@ -109,13 +112,10 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => { renderInput={params => ( setInputValue(e.target.value)} error={inputError} - helperText={ - helperText ?? - "Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters" - } + helperText={helperText ?? t('fields.entityTagsPicker.description')} FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }} /> )} diff --git a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx index 4e54ee99ad..74af578a21 100644 --- a/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/MyGroupsPicker/MyGroupsPicker.test.tsx @@ -15,10 +15,10 @@ */ import React from 'react'; -import { render, waitFor } from '@testing-library/react'; +import { waitFor } from '@testing-library/react'; import { CatalogApi } from '@backstage/catalog-client'; import { MyGroupsPicker } from './MyGroupsPicker'; -import { TestApiProvider } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { catalogApiRef, entityPresentationApiRef, @@ -115,7 +115,7 @@ describe('', () => { required, } as unknown as FieldProps; - render( + await renderInTestApp( ', () => { required, } as unknown as FieldProps; - const { queryByText, getByRole } = render( + const { queryByText, getByRole } = await renderInTestApp( ', () => { required, } as unknown as FieldProps; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( ', () => { formData: 'group:default/group1', } as unknown as FieldProps; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( { + const { t } = useTranslationRef(scaffolderTranslationRef); const { - schema: { title, description }, + schema: { + title = t('fields.myGroupsPicker.title'), + description = t('fields.myGroupsPicker.description'), + }, required, rawErrors, onChange, diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx index 02c437ceb6..06fdf17824 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx @@ -23,6 +23,8 @@ import { EntityPicker } from '../EntityPicker/EntityPicker'; import { OwnedEntityPickerProps } from './schema'; import { EntityPickerProps } from '../EntityPicker/schema'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { OwnedEntityPickerSchema } from './schema'; @@ -33,8 +35,12 @@ export { OwnedEntityPickerSchema } from './schema'; * @public */ export const OwnedEntityPicker = (props: OwnedEntityPickerProps) => { + const { t } = useTranslationRef(scaffolderTranslationRef); const { - schema: { title = 'Entity', description = 'An entity from the catalog' }, + schema: { + title = t('fields.ownedEntityPicker.title'), + description = t('fields.ownedEntityPicker.description'), + }, uiSchema, required, } = props; diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx index 5c69e73e12..792685f040 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx @@ -16,6 +16,8 @@ import React from 'react'; import { EntityPicker } from '../EntityPicker/EntityPicker'; import { OwnerPickerProps } from './schema'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export { OwnerPickerSchema } from './schema'; @@ -26,8 +28,12 @@ export { OwnerPickerSchema } from './schema'; * @public */ export const OwnerPicker = (props: OwnerPickerProps) => { + const { t } = useTranslationRef(scaffolderTranslationRef); const { - schema: { title = 'Owner', description = 'The owner of the component' }, + schema: { + title = t('fields.ownerPicker.title'), + description = t('fields.ownerPicker.description'), + }, uiSchema, ...restProps } = props; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx index 0380ac6ce8..904fb615eb 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx @@ -16,11 +16,12 @@ import React from 'react'; import { AzureRepoPicker } from './AzureRepoPicker'; -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; describe('AzureRepoPicker', () => { it('renders the two input fields', async () => { - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ); @@ -30,9 +31,9 @@ describe('AzureRepoPicker', () => { }); describe('org field', () => { - it('calls onChange when the organisation changes', () => { + it('calls onChange when the organisation changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ); @@ -45,9 +46,9 @@ describe('AzureRepoPicker', () => { }); describe('project field', () => { - it('calls onChange when the project changes', () => { + it('calls onChange when the project changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx index c7b0f1f887..59e666482a 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx @@ -20,6 +20,8 @@ import FormHelperText from '@material-ui/core/FormHelperText'; import TextField from '@material-ui/core/TextField'; import { BaseRepoUrlPickerProps } from './types'; import { Select, SelectItem } from '@backstage/core-components'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export const AzureRepoPicker = ( props: BaseRepoUrlPickerProps<{ @@ -34,6 +36,7 @@ export const AzureRepoPicker = ( state, onChange, } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const organizationItems: SelectItem[] = allowedOrganizations ? allowedOrganizations.map(i => ({ label: i, value: i })) @@ -53,27 +56,30 @@ export const AzureRepoPicker = ( error={rawErrors?.length > 0 && !organization} > {allowedOrganizations?.length ? ( - + onChange({ organization: String(Array.isArray(s) ? s[0] : s) }) + } + disabled={allowedOrganizations.length === 1} + selected={organization} + items={organizationItems} + /> + + {t('fields.azureRepoPicker.organization.description')} + + ) : ( onChange({ organization: e.target.value })} + helperText={t('fields.azureRepoPicker.organization.description')} value={organization} /> )} - - The Organization that this repo will belong to - 0 && !project} > {allowedProject?.length ? ( - + onChange({ project: String(Array.isArray(s) ? s[0] : s) }) + } + disabled={allowedProject.length === 1} + selected={project} + items={projectItems} + /> + + {t('fields.azureRepoPicker.project.description')} + + ) : ( onChange({ project: e.target.value })} value={project} + helperText={t('fields.azureRepoPicker.project.description')} /> )} - - The Project that this repo will belong to - ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx index 570c5bd664..05d04886e4 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.test.tsx @@ -16,9 +16,9 @@ import React from 'react'; import { BitbucketRepoPicker } from './BitbucketRepoPicker'; -import { render, fireEvent, waitFor } from '@testing-library/react'; +import { fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { TestApiProvider } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { ScaffolderApi, scaffolderApiRef, @@ -36,7 +36,7 @@ describe('BitbucketRepoPicker', () => { it('renders a select if there is a list of allowed owners', async () => { const allowedOwners = ['owner1', 'owner2']; - const { findByText } = render( + const { findByText } = await renderInTestApp( { expect(await findByText('owner2')).toBeInTheDocument(); }); - it('renders workspace input when host is bitbucket.org', () => { + it('renders workspace input when host is bitbucket.org', async () => { const state = { host: 'bitbucket.org', workspace: 'lolsWorkspace' }; - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { expect(getAllByRole('textbox')[0]).toHaveValue('lolsWorkspace'); }); - it('hides the workspace input when the host is not bitbucket.org', () => { + it('hides the workspace input when the host is not bitbucket.org', async () => { const state = { host: 'mycustom.domain.bitbucket.org', }; - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { }); describe('workspace field', () => { - it('calls onChange when the workspace changes', () => { + it('calls onChange when the workspace changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { }); describe('project field', () => { - it('calls onChange when the project changes', () => { + it('calls onChange when the project changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { }); it('Does not render a select if the list of allowed projects does not exist', async () => { - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { }); it('Does not render a select if the list of allowed projects is empty', async () => { - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { it('Does render a select if there is a list of allowed projects', async () => { const allowedProjects = ['project1', 'project2']; - const { findByText } = render( + const { findByText } = await renderInTestApp( { it('should populate workspaces if host is set and accessToken is provided', async () => { const onChange = jest.fn(); - const { getAllByRole, getByText } = render( + const { getAllByRole, getByText } = await renderInTestApp( { it('should populate projects if host and workspace are set and accessToken is provided', async () => { const onChange = jest.fn(); - const { getAllByRole, getByText } = render( + const { getAllByRole, getByText } = await renderInTestApp( { it('should populate repositories if host, workspace and project are set and accessToken is provided', async () => { const onChange = jest.fn(); - render( + await renderInTestApp( ({ label: i, value: i })) @@ -164,7 +168,7 @@ export const BitbucketRepoPicker = ( {allowedOwners?.length ? ( onChange({ project: String(Array.isArray(s) ? s[0] : s) }) } @@ -215,14 +223,18 @@ export const BitbucketRepoPicker = ( }} options={availableProjects} renderInput={params => ( - + )} freeSolo autoSelect /> )} - The Project that this repo will belong to + {t('fields.bitbucketRepoPicker.project.description')} diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx index f51d00adf2..f00e286418 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx @@ -16,13 +16,14 @@ import React from 'react'; import { GerritRepoPicker } from './GerritRepoPicker'; -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; describe('GerritRepoPicker', () => { describe('owner input field', () => { - it('calls onChange when the owner input changes', () => { + it('calls onChange when the owner input changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { }); describe('parent field', () => { - it('calls onChange when the parent changes', () => { + it('calls onChange when the parent changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( { const { onChange, rawErrors, state } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const { workspace, owner } = state; return ( <> 0 && !workspace}> onChange({ owner: e.target.value })} + helperText={t('fields.gerritRepoPicker.owner.description')} value={owner} /> - The owner of the project (optional) { > onChange({ workspace: e.target.value })} value={workspace} + helperText={t('fields.gerritRepoPicker.parent.description')} /> - - The project parent that the repo will belong to - ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx index e66ca49af1..056d8f0d11 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx @@ -16,13 +16,14 @@ import React from 'react'; import { GiteaRepoPicker } from './GiteaRepoPicker'; -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; describe('GiteaRepoPicker', () => { describe('owner input field', () => { - it('calls onChange when the owner input changes', () => { + it('calls onChange when the owner input changes', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ) => { const { allowedOwners = [], state, onChange, rawErrors } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const ownerItems: SelectItem[] = allowedOwners ? allowedOwners.map(i => ({ label: i, value: i })) : [{ label: 'Loading...', value: 'loading' }]; @@ -41,32 +44,36 @@ export const GiteaRepoPicker = ( error={rawErrors?.length > 0 && !owner} > {allowedOwners?.length ? ( - + onChange({ + owner: String( + Array.isArray(selected) ? selected[0] : selected, + ), + }) + } + disabled={allowedOwners.length === 1} + selected={owner} + items={ownerItems} + /> + + {t('fields.giteaRepoPicker.owner.description')} + + ) : ( <> onChange({ owner: e.target.value })} + helperText={t('fields.giteaRepoPicker.owner.description')} value={owner} /> )} - - Gitea namespace where this repository will belong to. It can be the - name of organization, group, subgroup, user, or the project. - ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx index 63bc5b8013..3a01c6de7c 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.test.tsx @@ -16,13 +16,14 @@ import React from 'react'; import { GithubRepoPicker } from './GithubRepoPicker'; -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; describe('GithubRepoPicker', () => { describe('owner field', () => { it('renders a select if there is a list of allowed owners', async () => { const allowedOwners = ['owner1', 'owner2']; - const { findByText } = render( + const { findByText } = await renderInTestApp( { it('calls onChange when the owner is changed to a different owner', async () => { const onChange = jest.fn(); const allowedOwners = ['owner1', 'owner2']; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( { expect(onChange).toHaveBeenCalledWith({ owner: 'owner2' }); }); - it('is disabled picked when only one allowed owner', () => { + it('is disabled picked when only one allowed owner', async () => { const onChange = jest.fn(); const allowedOwners = ['owner1']; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( { it('should display free text if no allowed owners are passed', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ) => { const { allowedOwners = [], rawErrors, state, onChange } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const ownerItems: SelectItem[] = allowedOwners ? allowedOwners.map(i => ({ label: i, value: i })) : [{ label: 'Loading...', value: 'loading' }]; @@ -40,27 +43,30 @@ export const GithubRepoPicker = ( error={rawErrors?.length > 0 && !owner} > {allowedOwners?.length ? ( - + onChange({ owner: String(Array.isArray(s) ? s[0] : s) }) + } + disabled={allowedOwners.length === 1} + selected={owner} + items={ownerItems} + /> + + {t('fields.githubRepoPicker.owner.description')} + + ) : ( onChange({ owner: e.target.value })} + helperText={t('fields.githubRepoPicker.owner.description')} value={owner} /> )} - - The organization, user or project that this repo will belong to - ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx index ad5c7f765e..f3d6e2da91 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx @@ -16,13 +16,14 @@ import React from 'react'; import { GitlabRepoPicker } from './GitlabRepoPicker'; -import { render, fireEvent } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; describe('GitlabRepoPicker', () => { describe('owner field', () => { it('renders a select if there is a list of allowed owners', async () => { const allowedOwners = ['owner1', 'owner2']; - const { findByText } = render( + const { findByText } = await renderInTestApp( { it('calls onChange when the owner is changed to a different owner', async () => { const onChange = jest.fn(); const allowedOwners = ['owner1', 'owner2']; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( { expect(onChange).toHaveBeenCalledWith({ owner: 'owner2' }); }); - it('is disabled picked when only one allowed owner', () => { + it('is disabled picked when only one allowed owner', async () => { const onChange = jest.fn(); const allowedOwners = ['owner1']; - const { getByRole } = render( + const { getByRole } = await renderInTestApp( { it('should display free text if no allowed owners are passed', async () => { const onChange = jest.fn(); - const { getAllByRole } = render( + const { getAllByRole } = await renderInTestApp( , ) => { const { allowedOwners = [], state, onChange, rawErrors } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const ownerItems: SelectItem[] = allowedOwners ? allowedOwners.map(i => ({ label: i, value: i })) : [{ label: 'Loading...', value: 'loading' }]; @@ -41,30 +44,34 @@ export const GitlabRepoPicker = ( error={rawErrors?.length > 0 && !owner} > {allowedOwners?.length ? ( - + onChange({ + owner: String( + Array.isArray(selected) ? selected[0] : selected, + ), + }) + } + disabled={allowedOwners.length === 1} + selected={owner} + items={ownerItems} + /> + + {t('fields.gitlabRepoPicker.owner.description')} + + ) : ( onChange({ owner: e.target.value })} + helperText={t('fields.gitlabRepoPicker.owner.description')} value={owner} /> )} - - GitLab namespace where this repository will belong to. It can be the - name of organization, group, subgroup, user, or the project. - ); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx index ed06983f90..86adc5ee20 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx @@ -20,6 +20,8 @@ import FormHelperText from '@material-ui/core/FormHelperText'; import { useApi } from '@backstage/core-plugin-api'; import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; import useAsync from 'react-use/esm/useAsync'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../../translation'; export const RepoUrlPickerHost = (props: { host?: string; @@ -28,6 +30,7 @@ export const RepoUrlPickerHost = (props: { rawErrors: string[]; }) => { const { host, hosts, onChange, rawErrors } = props; + const { t } = useTranslationRef(scaffolderTranslationRef); const scaffolderApi = useApi(scaffolderApiRef); const { value: { integrations } = { integrations: [] }, loading } = useAsync( @@ -73,7 +76,7 @@ export const RepoUrlPickerHost = (props: { onChange(String(Array.isArray(selected) ? selected[0] : selected)) } @@ -69,13 +72,19 @@ export const RepoUrlPickerRepoName = (props: { }} options={availableRepos || []} renderInput={params => ( - + )} freeSolo autoSelect /> )} - The name of the repository + + {t('fields.repoUrlPickerRepoName.repository.description')} + ); diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx index bddf891ec6..041ef9483f 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx @@ -33,6 +33,8 @@ import { Form } from '@backstage/plugin-scaffolder-react/alpha'; import { TemplateEditorForm } from './TemplateEditorForm'; import validator from '@rjsf/validator-ajv8'; import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ root: { @@ -68,6 +70,7 @@ export const CustomFieldExplorer = ({ onClose?: () => void; }) => { const classes = useStyles(); + const { t } = useTranslationRef(scaffolderTranslationRef); const fieldOptions = customFieldExtensions.filter(field => !!field.schema); const [selectedField, setSelectedField] = useState(fieldOptions[0]); const [fieldFormState, setFieldFormState] = useState({}); @@ -120,11 +123,11 @@ export const CustomFieldExplorer = ({
- Choose Custom Field Extension + {t('templateEditorPage.customFieldExplorer.selectFieldLabel')} handleSelectChange(e.target.value)} > diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx index b1508e43fc..400bc9d192 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx @@ -52,6 +52,11 @@ import { } from '../../routes'; import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model'; import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; +import { + TranslationFunction, + useTranslationRef, +} from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; /** * @alpha @@ -74,17 +79,13 @@ export type TemplateListPageProps = { }; }; -const defaultGroup: TemplateGroupFilter = { - title: 'Templates', - filter: () => true, -}; - const createGroupsWithOther = ( groups: TemplateGroupFilter[], + t: TranslationFunction, ): TemplateGroupFilter[] => [ ...groups, { - title: 'Other Templates', + title: t('templateListPage.templateGroups.otherTitle'), filter: e => ![...groups].some(({ filter }) => filter(e)), }, ]; @@ -107,10 +108,16 @@ export const TemplateListPage = (props: TemplateListPageProps) => { const viewTechDocsLink = useRouteRef(viewTechDocRouteRef); const templateRoute = useRouteRef(selectedTemplateRouteRef); const app = useApp(); + const { t } = useTranslationRef(scaffolderTranslationRef); const groups = givenGroups.length - ? createGroupsWithOther(givenGroups) - : [defaultGroup]; + ? createGroupsWithOther(givenGroups, t) + : [ + { + title: t('templateListPage.templateGroups.defaultTitle'), + filter: () => true, + }, + ]; const scaffolderPageContextMenuProps = { onEditorClicked: @@ -137,13 +144,15 @@ export const TemplateListPage = (props: TemplateListPageProps) => { ? [ { icon: app.getSystemIcon('docs') ?? DocsIcon, - text: 'View TechDocs', + text: t( + 'templateListPage.additionalLinksForEntity.viewTechDocsTitle', + ), url: viewTechDocsLink({ kind, namespace, name }), }, ] : []; }, - [app, viewTechDocsLink], + [app, viewTechDocsLink, t], ); const onTemplateSelected = useCallback( @@ -159,23 +168,23 @@ export const TemplateListPage = (props: TemplateListPageProps) => {
- + - Create new software components using standard templates. Different - templates create different kinds of components (services, - websites, documentation, ...). + {t('templateListPage.contentHeader.supportButtonTitle')} diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx index aef6ca6bb1..0524b99f8f 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -45,6 +45,8 @@ import { scaffolderTaskRouteRef, selectedTemplateRouteRef, } from '../../routes'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; import { TemplateWizardPageContextMenu } from './TemplateWizardPageContextMenu'; @@ -75,6 +77,7 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => { const { templateName, namespace } = useRouteRefParams( selectedTemplateRouteRef, ); + const { t } = useTranslationRef(scaffolderTranslationRef); const templateRef = stringifyEntityRef({ kind: 'Template', @@ -103,9 +106,9 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPageContextMenu.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPageContextMenu.tsx index c4927d5d14..50e406112a 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPageContextMenu.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPageContextMenu.tsx @@ -24,6 +24,8 @@ import { makeStyles } from '@material-ui/core/styles'; import Edit from '@material-ui/icons/Edit'; import MoreVert from '@material-ui/icons/MoreVert'; import React, { useState } from 'react'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { scaffolderTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ button: { @@ -41,6 +43,7 @@ export function TemplateWizardPageContextMenu( const { editUrl } = props; const classes = useStyles(); const [anchorEl, setAnchorEl] = useState(); + const { t } = useTranslationRef(scaffolderTranslationRef); if (!editUrl) { return null; @@ -83,7 +86,11 @@ export function TemplateWizardPageContextMenu( - + diff --git a/plugins/scaffolder/src/translation.ts b/plugins/scaffolder/src/translation.ts new file mode 100644 index 0000000000..eb6e4d005a --- /dev/null +++ b/plugins/scaffolder/src/translation.ts @@ -0,0 +1,286 @@ +/* + * 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 scaffolderTranslationRef = createTranslationRef({ + id: 'scaffolder', + messages: { + actionsPage: { + title: 'Installed actions', + pageTitle: 'Create a New Component', + subtitle: 'This is the collection of all installed actions', + content: { + emptyState: { + title: 'No information to display', + description: + 'There are no actions installed or there was an issue communicating with backend.', + }, + tableCell: { + name: 'Name', + title: 'Title', + description: 'Description', + type: 'Type', + }, + noRowsDescription: 'No schema defined', + }, + action: { + input: 'Input', + output: 'Output', + examples: 'Examples', + }, + }, + fields: { + entityNamePicker: { + title: 'Name', + description: 'Unique name of the component', + }, + entityPicker: { + title: 'Entity', + description: 'An entity from the catalog', + }, + entityTagsPicker: { + title: 'Tags', + description: + "Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters", + }, + myGroupsPicker: { + title: 'Entity', + description: 'An entity from the catalog', + }, + ownedEntityPicker: { + title: 'Entity', + description: 'An entity from the catalog', + }, + ownerPicker: { + title: 'Owner', + description: 'The owner of the component', + }, + azureRepoPicker: { + organization: { + title: 'Organization', + description: 'The Organization that this repo will belong to', + }, + project: { + title: 'Project', + description: 'The Project that this repo will belong to', + }, + }, + bitbucketRepoPicker: { + workspaces: { + title: 'Allowed Workspaces', + inputTitle: 'Workspaces', + description: 'The Workspace that this repo will belong to', + }, + project: { + title: 'Allowed Projects', + inputTitle: 'Projects', + description: 'The Project that this repo will belong to', + }, + }, + gerritRepoPicker: { + owner: { + title: 'Owner', + description: 'The owner of the project (optional)', + }, + parent: { + title: 'Parent', + description: 'The project parent that the repo will belong to', + }, + }, + giteaRepoPicker: { + owner: { + title: 'Owner Available', + inputTitle: 'Owner', + description: + 'Gitea namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.', + }, + }, + githubRepoPicker: { + owner: { + title: 'Owner Available', + inputTitle: 'Owner', + description: + 'The organization, user or project that this repo will belong to', + }, + }, + gitlabRepoPicker: { + owner: { + title: 'Owner Available', + inputTitle: 'Owner', + description: + 'GitLab namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.', + }, + }, + repoUrlPickerHost: { + host: { + title: 'Host', + description: 'The host where the repository will be created', + }, + }, + repoUrlPickerRepoName: { + repository: { + title: 'Repositories Available', + inputTitle: 'Repository', + description: 'The name of the repository', + }, + }, + }, + listTaskPage: { + title: 'List template tasks', + pageTitle: 'Templates Tasks', + subtitle: 'All tasks that have been started', + content: { + emptyState: { + title: 'No information to display', + description: + 'There are no tasks or there was an issue communicating with backend.', + }, + tableTitle: 'Tasks', + tableCell: { + taskID: 'Task ID', + template: 'Template', + created: 'Created', + owner: 'Owner', + status: 'Status', + }, + }, + }, + ownerListPicker: { + title: 'Task Owner', + options: { + owned: 'Owned', + all: 'All', + }, + }, + ongoingTask: { + title: 'Run of', + pageTitle: { + hasTemplateName: 'Run of {{templateName}}', + noTemplateName: 'Scaffolder Run', + }, + subtitle: 'Task {{taskId}}', + cancelButtonTitle: 'Cancel', + startOverButtonTitle: 'Start Over', + hideLogsButtonTitle: 'Hide Logs', + showLogsButtonTitle: 'Show Logs', + contextMenu: { + hideLogs: 'Hide Logs', + showLogs: 'Show Logs', + hideButtonBar: 'Hide Button Bar', + showButtonBar: 'Show Button Bar', + startOver: 'Start Over', + cancel: 'Cancel', + }, + }, + templateTypePicker: { + title: 'Categories', + }, + templateEditorPage: { + title: 'Template Editor', + subTitle: 'Edit, preview, and try out templates and template forms', + dryRunResults: { + title: 'Dry-run results', + }, + dryRunResultsList: { + title: 'Result {{resultId}}', + downloadButtonTitle: 'Download as .zip', + deleteButtonTitle: 'Delete result', + }, + dryRunResultsView: { + tab: { + files: 'Files', + log: 'Log', + output: 'Output', + }, + }, + taskStatusStepper: { + skippedStepTitle: 'Skipped', + }, + customFieldExplorer: { + selectFieldLabel: 'Choose Custom Field Extension', + fieldForm: { + title: 'Field Options', + applyButtonTitle: 'Apply', + }, + preview: { + title: 'Example Template Spec', + }, + }, + templateEditorBrowser: { + closeConfirmMessage: 'Are you sure? Unsaved changes will be lost', + saveIconTooltip: 'Save all files', + reloadIconTooltip: 'Reload directory', + closeIconTooltip: 'Close directory', + }, + templateEditorIntro: { + title: 'Get started by choosing one of the options below', + loadLocal: { + title: 'Load Template Directory', + description: + 'Load a local template directory, allowing you to both edit and try executing your own template.', + unSupportsLoadTooltip: + 'Only supported in some Chromium-based browsers', + }, + formEditor: { + title: 'Edit Template Form', + description: + 'Preview and edit a template form, either using a sample template or by loading a template from the catalog.', + }, + fieldExplorer: { + title: 'Custom Field Explorer', + description: + 'View and play around with available installed custom field extensions.', + }, + }, + templateEditorTextArea: { + saveIconTooltip: 'Save file', + refreshIconTooltip: 'Reload file', + }, + templateFormPreviewer: { + title: 'Load Existing Template', + }, + }, + templateListPage: { + title: 'Create a new component', + subtitle: + 'Create new software components using standard templates in your organization', + pageTitle: 'Create a new component', + templateGroups: { + defaultTitle: 'Templates', + otherTitle: 'Other Templates', + }, + contentHeader: { + title: 'Available Templates', + registerExistingButtonTitle: 'Register Existing Component', + supportButtonTitle: + 'Create new software components using standard templates. Different templates create different kinds of components (services, websites, documentation, ...).', + }, + additionalLinksForEntity: { + viewTechDocsTitle: 'View TechDocs', + }, + }, + templateWizardPage: { + title: 'Create a new component', + subtitle: + 'Create new software components using standard templates in your organization', + pageTitle: 'Create a new component', + pageContextMenu: { + editConfigurationTitle: 'Edit Configuration', + }, + }, + }, +});