Skip to content

Commit bc88e5f

Browse files
feat(i18n): add translation support to quickstart plugin with German, French and Spanish translations (#1397)
1 parent 5c8f1d0 commit bc88e5f

23 files changed

Lines changed: 584 additions & 27 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-quickstart': minor
3+
---
4+
5+
Add internationalization (i18n) support with German, French and Spanish translations in quickstart.

workspaces/quickstart/packages/app/src/App.test.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,63 @@
1616
import { render, waitFor } from '@testing-library/react';
1717
import App from './App';
1818

19+
// Mock HTMLCanvasElement.getContext to avoid canvas-related errors in tests
20+
Object.defineProperty(HTMLCanvasElement.prototype, 'getContext', {
21+
value: jest.fn(() => ({
22+
fillRect: jest.fn(),
23+
clearRect: jest.fn(),
24+
getImageData: jest.fn(() => ({
25+
data: new Array(4),
26+
})),
27+
putImageData: jest.fn(),
28+
createImageData: jest.fn(() => []),
29+
setTransform: jest.fn(),
30+
drawImage: jest.fn(),
31+
save: jest.fn(),
32+
fillText: jest.fn(),
33+
restore: jest.fn(),
34+
beginPath: jest.fn(),
35+
moveTo: jest.fn(),
36+
lineTo: jest.fn(),
37+
closePath: jest.fn(),
38+
stroke: jest.fn(),
39+
translate: jest.fn(),
40+
scale: jest.fn(),
41+
rotate: jest.fn(),
42+
arc: jest.fn(),
43+
fill: jest.fn(),
44+
measureText: jest.fn(() => ({ width: 0 })),
45+
transform: jest.fn(),
46+
rect: jest.fn(),
47+
clip: jest.fn(),
48+
createLinearGradient: jest.fn(() => ({
49+
addColorStop: jest.fn(),
50+
})),
51+
})),
52+
});
53+
54+
// eslint-disable-next-line no-console
55+
const originalError = console.error;
56+
beforeAll(() => {
57+
// eslint-disable-next-line no-console
58+
console.error = (...args: any[]) => {
59+
if (
60+
typeof args[0] === 'string' &&
61+
(args[0].includes('MUI: The') ||
62+
args[0].includes('Warning: findDOMNode') ||
63+
args[0].includes('Not implemented: HTMLCanvasElement'))
64+
) {
65+
return;
66+
}
67+
originalError.call(console, ...args);
68+
};
69+
});
70+
71+
afterAll(() => {
72+
// eslint-disable-next-line no-console
73+
console.error = originalError;
74+
});
75+
1976
describe('App', () => {
2077
it('should render', async () => {
2178
process.env = {

workspaces/quickstart/packages/app/src/App.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,15 @@ import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
5151
import { RequirePermission } from '@backstage/plugin-permission-react';
5252
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
5353
import { getAllThemes } from '@red-hat-developer-hub/backstage-plugin-theme';
54+
import { quickstartTranslations } from '@red-hat-developer-hub/backstage-plugin-quickstart';
5455

5556
const app = createApp({
5657
apis,
58+
__experimentalTranslations: {
59+
defaultLanguage: 'en',
60+
availableLanguages: ['en', 'de', 'fr', 'es'],
61+
resources: [quickstartTranslations],
62+
},
5763
bindRoutes({ bind }) {
5864
bind(catalogPlugin.externalRoutes, {
5965
createComponent: scaffolderPlugin.routes.root,

workspaces/quickstart/plugins/quickstart/dev/index.tsx

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
import { createDevApp } from '@backstage/dev-utils';
1818
import { quickstartPlugin, QuickstartDrawerProvider } from '../src/plugin';
19+
import { quickstartTranslations } from '../src/translations';
1920
import { useQuickstartDrawerContext } from '../src/hooks/useQuickstartDrawerContext';
21+
import { useTranslation } from '../src/hooks/useTranslation';
2022
import Box from '@mui/material/Box';
2123
import Typography from '@mui/material/Typography';
2224
import Card from '@mui/material/Card';
@@ -27,26 +29,28 @@ import { getAllThemes } from '@red-hat-developer-hub/backstage-plugin-theme';
2729
const QuickstartTestPageContent = () => {
2830
const { openDrawer, closeDrawer, isDrawerOpen } =
2931
useQuickstartDrawerContext();
32+
const { t } = useTranslation();
3033

3134
return (
3235
<Box sx={{ padding: 4 }}>
3336
<Typography variant="h3" gutterBottom>
34-
Quickstart Plugin Test Page
37+
{t('dev.pageTitle')}
3538
</Typography>
3639

3740
<Typography variant="body1" paragraph>
38-
This is a test page for the Quickstart plugin. Use the buttons below to
39-
interact with the quickstart drawer.
41+
{t('dev.pageDescription')}
4042
</Typography>
4143

4244
<Card sx={{ maxWidth: 600, marginBottom: 2 }}>
4345
<CardContent>
4446
<Typography variant="h6" gutterBottom>
45-
Drawer Controls
47+
{t('dev.drawerControls')}
4648
</Typography>
4749

4850
<Typography variant="body2" color="text.secondary" paragraph>
49-
Current drawer state: {isDrawerOpen ? 'Open' : 'Closed'}
51+
{t('dev.currentState' as any, {
52+
state: isDrawerOpen ? t('dev.stateOpen') : t('dev.stateClosed'),
53+
})}
5054
</Typography>
5155

5256
<Box sx={{ display: 'flex', gap: 2 }}>
@@ -56,15 +60,15 @@ const QuickstartTestPageContent = () => {
5660
onClick={openDrawer}
5761
disabled={isDrawerOpen}
5862
>
59-
Open Quickstart Guide
63+
{t('button.openQuickstartGuide')}
6064
</Button>
6165

6266
<Button
6367
variant="outlined"
6468
onClick={closeDrawer}
6569
disabled={!isDrawerOpen}
6670
>
67-
Close Drawer
71+
{t('button.closeDrawer')}
6872
</Button>
6973
</Box>
7074
</CardContent>
@@ -73,19 +77,18 @@ const QuickstartTestPageContent = () => {
7377
<Card sx={{ maxWidth: 600 }}>
7478
<CardContent>
7579
<Typography variant="h6" gutterBottom>
76-
Instructions
80+
{t('dev.instructions')}
7781
</Typography>
7882
<Typography variant="body2" color="text.secondary">
79-
1. Click "Open Quickstart Guide" to open the drawer
83+
{t('dev.step1')}
8084
<br />
81-
2. Navigate through the quickstart steps
85+
{t('dev.step2')}
8286
<br />
83-
3. Test the progress tracking by completing steps
87+
{t('dev.step3')}
8488
<br />
85-
4. The drawer can be closed using the close button or the drawer's
86-
own controls
89+
{t('dev.step4')}
8790
<br />
88-
5. Progress is automatically saved to localStorage
91+
{t('dev.step5')}
8992
</Typography>
9093
</CardContent>
9194
</Card>
@@ -101,6 +104,9 @@ const QuickstartTestPage = () => (
101104

102105
createDevApp()
103106
.registerPlugin(quickstartPlugin)
107+
.addTranslationResource(quickstartTranslations)
108+
.setAvailableLanguages(['en', 'de', 'fr', 'es'])
109+
.setDefaultLanguage('en')
104110
.addThemes(getAllThemes())
105111
.addPage({
106112
element: <QuickstartTestPage />,

workspaces/quickstart/plugins/quickstart/report.api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { BackstagePlugin } from '@backstage/core-plugin-api';
99
import { CSSProperties } from 'react';
1010
import { PropsWithChildren } from 'react';
11+
import { TranslationResource } from '@backstage/core-plugin-api/alpha';
1112

1213
// @public
1314
export const QuickstartButton: React.ComponentType<QuickstartButtonProps>;
@@ -35,6 +36,9 @@ export const QuickstartDrawerProvider: React.ComponentType<PropsWithChildren>;
3536
// @public
3637
export const quickstartPlugin: BackstagePlugin<{}, {}, {}>;
3738

39+
// @public
40+
export const quickstartTranslations: TranslationResource<'plugin.quickstart'>;
41+
3842
// @public
3943
export const useQuickstartDrawerContext: () => QuickstartDrawerContextType;
4044

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
export * from './translations';

workspaces/quickstart/plugins/quickstart/src/components/QuickstartButton/QuickstartButton.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { useTheme } from '@mui/material/styles';
2222
import WavingHandIcon from '@mui/icons-material/WavingHandOutlined';
2323
import { useQuickstartPermission } from '../../hooks/useQuickstartPermission';
2424
import { useQuickstartDrawerContext } from '../../hooks/useQuickstartDrawerContext';
25+
import { useTranslation } from '../../hooks/useTranslation';
2526

2627
/**
2728
* Props for the QuickstartButton component
@@ -47,14 +48,17 @@ export interface QuickstartButtonProps {
4748
* @public
4849
*/
4950
export const QuickstartButton = ({
50-
title = 'Quick start',
51+
title,
5152
style,
5253
onClick = () => {},
5354
}: QuickstartButtonProps) => {
55+
const { t } = useTranslation();
5456
const isAllowed = useQuickstartPermission();
5557
const { toggleDrawer } = useQuickstartDrawerContext();
5658
const theme = useTheme();
5759

60+
const defaultTitle = t('button.quickstart');
61+
5862
const handleClick = useCallback(() => {
5963
toggleDrawer();
6064
onClick();
@@ -102,7 +106,7 @@ export const QuickstartButton = ({
102106
}}
103107
>
104108
<Typography variant="body2" color={theme.palette.text.primary}>
105-
{title}
109+
{title || defaultTitle}
106110
</Typography>
107111
</Box>
108112
</Box>

workspaces/quickstart/plugins/quickstart/src/components/QuickstartContent/QuickstartContent.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { QuickstartItem } from './QuickstartItem';
2020
import { EmptyState } from '@backstage/core-components';
2121
import { useState } from 'react';
2222
import { QuickstartItemData } from '../../types';
23+
import { useTranslation } from '../../hooks/useTranslation';
2324

2425
type QuickstartContentProps = {
2526
quickstartItems: QuickstartItemData[];
@@ -32,6 +33,7 @@ export const QuickstartContent = ({
3233
setProgress,
3334
itemCount,
3435
}: QuickstartContentProps) => {
36+
const { t } = useTranslation();
3537
const [openItems, setOpenItems] = useState<boolean[]>(
3638
new Array(itemCount).fill(false),
3739
);
@@ -62,10 +64,7 @@ export const QuickstartContent = ({
6264
))}
6365
</List>
6466
) : (
65-
<EmptyState
66-
title="Quickstart content not available for your role."
67-
missing="data"
68-
/>
67+
<EmptyState title={t('content.emptyState.title')} missing="data" />
6968
)}
7069
</Box>
7170
);

workspaces/quickstart/plugins/quickstart/src/components/QuickstartContent/QuickstartItem.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { QuickstartItemIcon } from './QuickstartItemIcon';
2727
import { QuickstartCtaLink } from './QuickstartCtaLink';
2828
import IconButton from '@mui/material/IconButton';
2929
import { QuickstartItemData } from '../../types';
30+
import { useTranslation } from '../../hooks/useTranslation';
3031

3132
export type QuickstartItemProps = {
3233
item: QuickstartItemData;
@@ -43,6 +44,7 @@ export const QuickstartItem = ({
4344
open,
4445
handleOpen,
4546
}: QuickstartItemProps) => {
47+
const { t } = useTranslation();
4648
const [stepCompleted, setStepCompleted] = useState<boolean>(false);
4749
const itemKey = `${item.title}-${index}`;
4850

@@ -70,7 +72,11 @@ export const QuickstartItem = ({
7072
onClick={handleOpen}
7173
role="button"
7274
aria-expanded={open}
73-
aria-label={`${open ? 'Collapse' : 'Expand'} ${item.title} details`}
75+
aria-label={
76+
open
77+
? t('item.collapseAriaLabel' as any, { title: item.title })
78+
: t('item.expandAriaLabel' as any, { title: item.title })
79+
}
7480
tabIndex={0}
7581
onKeyDown={e => {
7682
if (e.key === 'Enter' || e.key === ' ') {
@@ -115,7 +121,11 @@ export const QuickstartItem = ({
115121
}}
116122
/>
117123
<IconButton
118-
aria-label={open ? 'Collapse item' : 'Expand item'}
124+
aria-label={
125+
open
126+
? t('item.collapseButtonAriaLabel')
127+
: t('item.expandButtonAriaLabel')
128+
}
119129
sx={{
120130
...(open
121131
? { color: theme => theme.palette.text.primary }

workspaces/quickstart/plugins/quickstart/src/components/QuickstartFooter.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Box from '@mui/material/Box';
1818
import Button from '@mui/material/Button';
1919
import LinearProgress from '@mui/material/LinearProgress';
2020
import Typography from '@mui/material/Typography';
21+
import { useTranslation } from '../hooks/useTranslation';
2122

2223
export type QuickstartFooterProps = {
2324
handleDrawerClose: () => void;
@@ -28,6 +29,8 @@ export const QuickstartFooter = ({
2829
handleDrawerClose,
2930
progress,
3031
}: QuickstartFooterProps) => {
32+
const { t } = useTranslation();
33+
3134
return (
3235
<Box>
3336
<LinearProgress variant="determinate" value={progress} />
@@ -40,9 +43,11 @@ export const QuickstartFooter = ({
4043
}}
4144
>
4245
<Typography sx={{ fontSize: theme => theme.typography.caption }}>
43-
{progress > 0 ? `${progress}% progress` : 'Not started'}
46+
{progress > 0
47+
? t('footer.progress' as any, { progress: progress.toString() })
48+
: t('footer.notStarted')}
4449
</Typography>
45-
<Button onClick={() => handleDrawerClose()}>Hide</Button>
50+
<Button onClick={() => handleDrawerClose()}>{t('footer.hide')}</Button>
4651
</Box>
4752
</Box>
4853
);

0 commit comments

Comments
 (0)