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(
<>