feat: home plugin support i18n

Signed-off-by: mario ma <mario.ma.node@gmail.com>
This commit is contained in:
mario ma
2025-06-04 15:55:51 +08:00
parent fbb4cf67eb
commit cef60db9c0
22 changed files with 257 additions and 43 deletions
@@ -23,6 +23,8 @@ import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import AddIcon from '@material-ui/icons/Add';
import ListItemText from '@material-ui/core/ListItemText';
import Typography from '@material-ui/core/Typography';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { homeTranslationRef } from '../../translation';
interface AddWidgetDialogProps {
widgets: Widget[];
@@ -35,9 +37,10 @@ const getTitle = (widget: Widget) => {
export const AddWidgetDialog = (props: AddWidgetDialogProps) => {
const { widgets, handleAdd } = props;
const { t } = useTranslationRef(homeTranslationRef);
return (
<>
<DialogTitle>Add new widget to dashboard</DialogTitle>
<DialogTitle>{t('addWidgetDialog.title')}</DialogTitle>
<DialogContent>
<List dense>
{widgets.map(widget => {
@@ -20,6 +20,8 @@ import DeleteIcon from '@material-ui/icons/Delete';
import AddIcon from '@material-ui/icons/Add';
import EditIcon from '@material-ui/icons/Edit';
import CancelIcon from '@material-ui/icons/Cancel';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { homeTranslationRef } from '../../translation';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -55,6 +57,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => {
restoreDefault,
} = props;
const styles = useStyles();
const { t } = useTranslationRef(homeTranslationRef);
return (
<>
@@ -66,7 +69,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => {
size="small"
startIcon={<EditIcon />}
>
Edit
{t('customHomepageButtons.edit')}
</Button>
) : (
<>
@@ -78,7 +81,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => {
size="small"
startIcon={<CancelIcon />}
>
Restore defaults
{t('customHomepageButtons.restoreDefaults')}
</Button>
)}
{numWidgets > 0 && (
@@ -90,7 +93,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => {
size="small"
startIcon={<DeleteIcon />}
>
Clear all
{t('customHomepageButtons.clearAll')}
</Button>
)}
<Button
@@ -100,7 +103,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => {
size="small"
startIcon={<AddIcon />}
>
Add widget
{t('customHomepageButtons.addWidget')}
</Button>
{numWidgets > 0 && (
<Button
@@ -111,7 +114,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => {
size="small"
startIcon={<SaveIcon />}
>
Save
{t('customHomepageButtons.save')}
</Button>
)}
</>
@@ -50,6 +50,8 @@ import {
WidgetSchema,
} from './types';
import { CardConfig } from '@backstage/plugin-home-react';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { homeTranslationRef } from '../../translation';
// eslint-disable-next-line new-cap
const ResponsiveGrid = WidthProvider(Responsive);
@@ -222,6 +224,7 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => {
const getWidgetNameFromKey = (key: string) => {
return key.split('__')[0];
};
const { t } = useTranslationRef(homeTranslationRef);
const handleAdd = (widget: Widget) => {
const widgetId = `${widget.name}__${widgets.length + 1}${Math.random()
@@ -340,7 +343,7 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => {
</Dialog>
{!editMode && widgets.length === 0 && (
<Typography variant="h5" align="center">
No widgets added. Start by clicking the 'Add widget' button.
{t('customHomepage.noWidgets')}
</Typography>
)}
<ResponsiveGrid
@@ -29,6 +29,8 @@ import { Widget } from './types';
import { withTheme } from '@rjsf/core';
import { Theme as MuiTheme } from '@rjsf/material-ui';
import validator from '@rjsf/validator-ajv8';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { homeTranslationRef } from '../../translation';
const Form = withTheme(MuiTheme);
@@ -68,6 +70,7 @@ export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => {
const styles = useStyles();
const onClose = () => setSettingsDialogOpen(false);
const { t } = useTranslationRef(homeTranslationRef);
return (
<div className={styles.settingsOverlay}>
@@ -98,10 +101,10 @@ export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => {
>
<DialogActions>
<Button color="primary" variant="contained" type="submit">
Submit
{t('widgetSettingsOverlay.submitButtonTitle')}
</Button>
<Button color="secondary" onClick={onClose}>
Cancel
{t('widgetSettingsOverlay.cancelButtonTitle')}
</Button>
</DialogActions>
</Form>
@@ -116,7 +119,7 @@ export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => {
>
{widget.settingsSchema && (
<Grid item className="overlayGridItem">
<Tooltip title="Edit settings">
<Tooltip title={t('widgetSettingsOverlay.editSettingsTooptip')}>
<IconButton
color="primary"
onClick={() => setSettingsDialogOpen(true)}
@@ -128,7 +131,7 @@ export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => {
)}
{deletable !== false && (
<Grid item className="overlayGridItem">
<Tooltip title="Delete widget">
<Tooltip title={t('widgetSettingsOverlay.deleteWidgetTooltip')}>
<IconButton color="secondary" onClick={() => handleRemove(id)}>
<DeleteIcon fontSize="large" />
</IconButton>
@@ -26,6 +26,8 @@ import { entityRouteRef } from '@backstage/plugin-catalog-react';
import { useRouteRef } from '@backstage/core-plugin-api';
import { FavoriteToggle } from '@backstage/core-components';
import { makeStyles } from '@material-ui/core/styles';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { homeTranslationRef } from '../../translation';
type EntityListItemProps = {
entity: Entity;
@@ -50,6 +52,7 @@ export const StarredEntityListItem = ({
}: EntityListItemProps) => {
const classes = useStyles();
const catalogEntityRoute = useRouteRef(entityRouteRef);
const { t } = useTranslationRef(homeTranslationRef);
let secondaryText = '';
if (showKind) {
@@ -80,7 +83,7 @@ export const StarredEntityListItem = ({
>
<FavoriteToggle
id={`remove-favorite-${entity.metadata.uid}`}
title="Remove entity from favorites"
title={t('starredEntityListItem.removeFavoriteEntityTitle')}
isFavorite
onToggle={() => onToggleStarredEntity(entity)}
/>
@@ -14,16 +14,20 @@
* limitations under the License.
*/
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import Typography from '@material-ui/core/Typography';
import { homeTranslationRef } from '../../translation';
export const VisitListEmpty = () => (
<>
<Typography variant="body2" color="textSecondary">
There are no visits to show yet.
</Typography>
<Typography variant="body2" color="textSecondary">
Once you start using Backstage, your visits will appear here as a quick
link to carry on where you left off.
</Typography>
</>
);
export const VisitListEmpty = () => {
const { t } = useTranslationRef(homeTranslationRef);
return (
<>
<Typography variant="body2" color="textSecondary">
{t('visitList.empty.title')}
</Typography>
<Typography variant="body2" color="textSecondary">
{t('visitList.empty.description')}
</Typography>
</>
);
};
@@ -14,12 +14,17 @@
* limitations under the License.
*/
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import Typography from '@material-ui/core/Typography';
import { homeTranslationRef } from '../../translation';
export const VisitListFew = () => (
<>
<Typography variant="body2" color="textSecondary">
The more pages you visit, the more pages will appear here.
</Typography>
</>
);
export const VisitListFew = () => {
const { t } = useTranslationRef(homeTranslationRef);
return (
<>
<Typography variant="body2" color="textSecondary">
{t('visitList.few.title')}
</Typography>
</>
);
};
@@ -33,6 +33,8 @@ import { EntityFilterQuery } from '@backstage/catalog-client';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { homeTranslationRef } from '../../translation';
/**
* Props customizing the <FeaturedDocsCard/> component.
@@ -88,7 +90,8 @@ const useStyles = makeStyles(
export const Content = (props: FeaturedDocsCardProps): JSX.Element => {
const { emptyState, filter, linkDestination, responseLimit, subLinkText } =
props;
const linkText = subLinkText || 'LEARN MORE';
const { t } = useTranslationRef(homeTranslationRef);
const linkText = subLinkText || t('featuredDocsCard.learnMoreTitle');
const styles = useStyles();
const catalogApi = useApi(catalogApiRef);
const {
@@ -153,14 +156,14 @@ export const Content = (props: FeaturedDocsCardProps): JSX.Element => {
emptyState || (
<EmptyState
missing="data"
title="No documents to show"
description="Create your own document. Check out our Getting Started Information"
title={t('featuredDocsCard.empty.title')}
description={t('featuredDocsCard.empty.description')}
action={
<LinkButton
to="https://backstage.io/docs/features/techdocs/getting-started"
variant="contained"
>
DOCS
{t('featuredDocsCard.empty.learnMoreLinkTitle')}
</LinkButton>
}
/>
@@ -20,6 +20,8 @@ import Typography from '@material-ui/core/Typography';
import Grid from '@material-ui/core/Grid';
import { ContentModal } from '@backstage/plugin-home-react';
import { useStyles } from './styles';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { homeTranslationRef } from '../../translation';
/**
* Props customizing the <QuickStartCard/> component.
@@ -54,14 +56,15 @@ export type QuickStartCardProps = {
*/
export const Content = (props: QuickStartCardProps): JSX.Element => {
const styles = useStyles();
const { t } = useTranslationRef(homeTranslationRef);
return (
<>
<ContentModal
modalContent={props.image}
linkContent={props.modalTitle || 'Onboarding'}
linkContent={props.modalTitle || t('quickStart.title')}
/>
<Typography variant="body1" paragraph>
{props.cardDescription || 'Get started with Backstage'}
{props.cardDescription || t('quickStart.description')}
</Typography>
<ContentModal modalContent={props.image} linkContent={props.image} />
<Grid
@@ -78,7 +81,7 @@ export const Content = (props: QuickStartCardProps): JSX.Element => {
variant="h6"
className={styles.link}
>
{props.docsLinkTitle || 'Learn more'}
{props.docsLinkTitle || t('quickStart.learnMoreLinkTitle')}
</Link>
</Grid>
</Grid>
@@ -29,6 +29,8 @@ import { ReactNode, useState } from 'react';
import useAsync from 'react-use/esm/useAsync';
import { StarredEntityListItem } from '../../components/StarredEntityListItem/StarredEntityListItem';
import { makeStyles } from '@material-ui/core/styles';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { homeTranslationRef } from '../../translation';
const useStyles = makeStyles(theme => ({
tabs: {
@@ -63,6 +65,7 @@ export const Content = ({
const catalogApi = useApi(catalogApiRef);
const { starredEntities, toggleStarredEntity } = useStarredEntities();
const [activeTab, setActiveTab] = useState(0);
const { t } = useTranslationRef(homeTranslationRef);
// Grab starred entities from catalog to ensure they still exist and also retrieve display titles
const entities = useAsync(async () => {
@@ -89,7 +92,7 @@ export const Content = ({
return (
<Typography variant="body1">
{noStarredEntitiesMessage ||
'Click the star beside an entity name to add it to this list!'}
t('starredEntities.noStarredEntitiesMessage')}
</Typography>
);
@@ -17,6 +17,8 @@
import { useCallback } from 'react';
import Button from '@material-ui/core/Button';
import { useContext } from './Context';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { homeTranslationRef } from '../../translation';
export const Actions = () => {
const { collapsed, setCollapsed, visits, numVisitsOpen, loading } =
@@ -25,7 +27,10 @@ export const Actions = () => {
() => setCollapsed(prevCollapsed => !prevCollapsed),
[setCollapsed],
);
const label = collapsed ? 'View More' : 'View Less';
const { t } = useTranslationRef(homeTranslationRef);
const label = collapsed
? t('visitedByType.action.viewMore')
: t('visitedByType.action.viewLess');
if (!loading && visits.length <= numVisitsOpen) return <></>;
+1
View File
@@ -42,3 +42,4 @@ export * from './assets';
export * from './homePageComponents';
export * from './deprecated';
export * from './api';
export { homeTranslationRef } from './translation';
+81
View File
@@ -0,0 +1,81 @@
/*
* Copyright 2025 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/frontend-plugin-api';
/**
* @public
*/
export const homeTranslationRef = createTranslationRef({
id: 'home',
messages: {
addWidgetDialog: {
title: 'Add new widget to dashboard',
},
customHomepageButtons: {
edit: 'Edit',
restoreDefaults: 'Restore defaults',
clearAll: 'Clear all',
addWidget: 'Add widget',
save: 'Save',
},
customHomepage: {
noWidgets: "No widgets added. Start by clicking the 'Add widget' button.",
},
widgetSettingsOverlay: {
editSettingsTooptip: 'Edit settings',
deleteWidgetTooltip: 'Delete widget',
submitButtonTitle: 'Submit',
cancelButtonTitle: 'Cancel',
},
starredEntityListItem: {
removeFavoriteEntityTitle: 'Remove entity from favorites',
},
visitList: {
empty: {
title: 'There are no visits to show yet.',
description:
'Once you start using Backstage, your visits will appear here as a quick link to carry on where you left off.',
},
few: {
title: 'The more pages you visit, the more pages will appear here.',
},
},
quickStart: {
title: 'Onboarding',
description: 'Get started with Backstage',
learnMoreLinkTitle: 'Learn more',
},
starredEntities: {
noStarredEntitiesMessage:
'Click the star beside an entity name to add it to this list!',
},
visitedByType: {
action: {
viewMore: 'View more',
viewLess: 'View less',
},
},
featuredDocsCard: {
learnMoreTitle: 'LEARN MORE',
empty: {
title: 'No documents to show',
description:
'Create your own document. Check out our Getting Started Information',
learnMoreLinkTitle: 'DOCS',
},
},
},
});