feat: org plugin support i18n
Signed-off-by: mario ma <mario.ma.node@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-org': patch
|
||||
---
|
||||
|
||||
Org plugin support i18n
|
||||
@@ -13,6 +13,7 @@ import { ExtensionInput } from '@backstage/frontend-plugin-api';
|
||||
import { ExternalRouteRef } from '@backstage/frontend-plugin-api';
|
||||
import { FrontendPlugin } from '@backstage/frontend-plugin-api';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
import { TranslationRef } from '@backstage/frontend-plugin-api';
|
||||
|
||||
// @alpha (undocumented)
|
||||
const _default: FrontendPlugin<
|
||||
@@ -203,5 +204,38 @@ const _default: FrontendPlugin<
|
||||
>;
|
||||
export default _default;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export const orgTranslationRef: TranslationRef<
|
||||
'org',
|
||||
{
|
||||
readonly 'groupProfileCard.groupNotFound': 'Group not found';
|
||||
readonly 'groupProfileCard.editIconButtonTitle': 'Edit Metadata';
|
||||
readonly 'groupProfileCard.refreshIconButtonTitle': 'Schedule entity refresh';
|
||||
readonly 'groupProfileCard.refreshIconButtonAriaLabel': 'Refresh';
|
||||
readonly 'groupProfileCard.listItemTitle.email': 'Email';
|
||||
readonly 'groupProfileCard.listItemTitle.entityRef': 'Entity Ref';
|
||||
readonly 'groupProfileCard.listItemTitle.parentGroup': 'Parent Group';
|
||||
readonly 'groupProfileCard.listItemTitle.childGroups': 'Child Groups';
|
||||
readonly 'membersListCard.title': 'Members';
|
||||
readonly 'membersListCard.subtitle': 'of {{groupName}}';
|
||||
readonly 'membersListCard.paginationLabel': ', page {{page}} of {{nbPages}}';
|
||||
readonly 'membersListCard.noMembersDescription': 'This group has no members.';
|
||||
readonly 'membersListCard.aggregateMembersToggle.ariaLabel': 'Users Type Switch';
|
||||
readonly 'membersListCard.aggregateMembersToggle.directMembers': 'Direct Members';
|
||||
readonly 'membersListCard.aggregateMembersToggle.aggregatedMembers': 'Aggregated Members';
|
||||
readonly 'ownershipCard.title': 'Ownership';
|
||||
readonly 'ownershipCard.aggregateRelationsToggle.ariaLabel': 'Ownership Type Switch';
|
||||
readonly 'ownershipCard.aggregateRelationsToggle.directRelations': 'Direct Relations';
|
||||
readonly 'ownershipCard.aggregateRelationsToggle.aggregatedRelations': 'Aggregated Relations';
|
||||
readonly 'userProfileCard.editIconButtonTitle': 'Edit Metadata';
|
||||
readonly 'userProfileCard.listItemTitle.email': 'Email';
|
||||
readonly 'userProfileCard.listItemTitle.memberOf': 'Member of';
|
||||
readonly 'userProfileCard.userNotFound': 'User not found';
|
||||
readonly 'userProfileCard.moreGroupButtonTitle': '...More ({{number}})';
|
||||
readonly 'userProfileCard.allGroupDialog.title': "All {{name}}'s groups:";
|
||||
readonly 'userProfileCard.allGroupDialog.closeButtonTitle': 'Close';
|
||||
}
|
||||
>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -98,3 +98,5 @@ export default createFrontendPlugin({
|
||||
catalogIndex: catalogIndexRouteRef,
|
||||
}),
|
||||
});
|
||||
|
||||
export { orgTranslationRef } from './translation';
|
||||
|
||||
@@ -55,6 +55,8 @@ import PermIdentityIcon from '@material-ui/icons/PermIdentity';
|
||||
import { LinksGroup } from '../../Meta';
|
||||
import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common/alpha';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
import { orgTranslationRef } from '../../../../translation';
|
||||
|
||||
const CardTitle = (props: { title: string }) => (
|
||||
<Box display="flex" alignItems="center">
|
||||
@@ -74,6 +76,7 @@ export const GroupProfileCard = (props: {
|
||||
const { allowed: canRefresh } = useEntityPermission(
|
||||
catalogEntityRefreshPermission,
|
||||
);
|
||||
const { t } = useTranslationRef(orgTranslationRef);
|
||||
|
||||
const refreshEntity = useCallback(async () => {
|
||||
await catalogApi.refreshEntity(stringifyEntityRef(group));
|
||||
@@ -85,7 +88,9 @@ export const GroupProfileCard = (props: {
|
||||
}, [catalogApi, alertApi, group]);
|
||||
|
||||
if (!group) {
|
||||
return <Alert severity="error">Group not found</Alert>;
|
||||
return (
|
||||
<Alert severity="error">{t('groupProfileCard.groupNotFound')}</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
@@ -111,15 +116,19 @@ export const GroupProfileCard = (props: {
|
||||
const emailHref = profile?.email ? `mailto:${profile.email}` : '#';
|
||||
const infoCardAction = entityMetadataEditUrl ? (
|
||||
<IconButton
|
||||
aria-label="Edit"
|
||||
title="Edit Metadata"
|
||||
aria-label={t('groupProfileCard.editIconButtonTitle')}
|
||||
title={t('groupProfileCard.editIconButtonTitle')}
|
||||
component={Link}
|
||||
to={entityMetadataEditUrl}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
) : (
|
||||
<IconButton aria-label="Edit" disabled title="Edit Metadata">
|
||||
<IconButton
|
||||
aria-label={t('groupProfileCard.editIconButtonTitle')}
|
||||
disabled
|
||||
title={t('groupProfileCard.editIconButtonTitle')}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
);
|
||||
@@ -133,8 +142,8 @@ export const GroupProfileCard = (props: {
|
||||
<>
|
||||
{allowRefresh && canRefresh && (
|
||||
<IconButton
|
||||
aria-label="Refresh"
|
||||
title="Schedule entity refresh"
|
||||
aria-label={t('groupProfileCard.refreshIconButtonAriaLabel')}
|
||||
title={t('groupProfileCard.refreshIconButtonTitle')}
|
||||
onClick={refreshEntity}
|
||||
>
|
||||
<CachedIcon />
|
||||
@@ -152,31 +161,33 @@ export const GroupProfileCard = (props: {
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<Tooltip title="Entity Ref">
|
||||
<Tooltip title={t('groupProfileCard.listItemTitle.entityRef')}>
|
||||
<PermIdentityIcon />
|
||||
</Tooltip>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={stringifyEntityRef(group)}
|
||||
secondary="Entity Ref"
|
||||
secondary={t('groupProfileCard.listItemTitle.entityRef')}
|
||||
/>
|
||||
</ListItem>
|
||||
{profile?.email && (
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<Tooltip title="Email">
|
||||
<Tooltip title={t('groupProfileCard.listItemTitle.email')}>
|
||||
<EmailIcon />
|
||||
</Tooltip>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={<Link to={emailHref}>{profile.email}</Link>}
|
||||
secondary="Email"
|
||||
secondary={t('groupProfileCard.listItemTitle.email')}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<Tooltip title="Parent Group">
|
||||
<Tooltip
|
||||
title={t('groupProfileCard.listItemTitle.parentGroup')}
|
||||
>
|
||||
<AccountTreeIcon />
|
||||
</Tooltip>
|
||||
</ListItemIcon>
|
||||
@@ -191,12 +202,14 @@ export const GroupProfileCard = (props: {
|
||||
'N/A'
|
||||
)
|
||||
}
|
||||
secondary="Parent Group"
|
||||
secondary={t('groupProfileCard.listItemTitle.parentGroup')}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<Tooltip title="Child Groups">
|
||||
<Tooltip
|
||||
title={t('groupProfileCard.listItemTitle.childGroups')}
|
||||
>
|
||||
<GroupIcon />
|
||||
</Tooltip>
|
||||
</ListItemIcon>
|
||||
@@ -211,7 +224,7 @@ export const GroupProfileCard = (props: {
|
||||
'N/A'
|
||||
)
|
||||
}
|
||||
secondary="Child Groups"
|
||||
secondary={t('groupProfileCard.listItemTitle.childGroups')}
|
||||
/>
|
||||
</ListItem>
|
||||
{props?.showLinks && <LinksGroup links={links} />}
|
||||
|
||||
@@ -48,6 +48,8 @@ import {
|
||||
removeDuplicateEntitiesFrom,
|
||||
} from '../../../../helpers/helpers';
|
||||
import { EntityRelationAggregation } from '../../types';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
import { orgTranslationRef } from '../../../../translation';
|
||||
|
||||
/** @public */
|
||||
export type MemberComponentClassKey = 'card' | 'avatar';
|
||||
@@ -157,8 +159,9 @@ export const MembersListCard = (props: {
|
||||
relationsType?: EntityRelationAggregation;
|
||||
relationAggregation?: EntityRelationAggregation;
|
||||
}) => {
|
||||
const { t } = useTranslationRef(orgTranslationRef);
|
||||
const {
|
||||
memberDisplayTitle = 'Members',
|
||||
memberDisplayTitle = t('membersListCard.title'),
|
||||
pageSize = 50,
|
||||
showAggregateMembersToggle,
|
||||
relationType = 'memberof',
|
||||
@@ -236,7 +239,13 @@ export const MembersListCard = (props: {
|
||||
}
|
||||
|
||||
const nbPages = Math.ceil((members?.length || 0) / pageSize);
|
||||
const paginationLabel = nbPages < 2 ? '' : `, page ${page} of ${nbPages}`;
|
||||
const paginationLabel =
|
||||
nbPages < 2
|
||||
? ''
|
||||
: t('membersListCard.paginationLabel', {
|
||||
page: String(page),
|
||||
nbPages: String(nbPages),
|
||||
});
|
||||
|
||||
const pagination = (
|
||||
<Pagination
|
||||
@@ -260,9 +269,7 @@ export const MembersListCard = (props: {
|
||||
} else {
|
||||
memberList = (
|
||||
<Box p={2}>
|
||||
<Typography>
|
||||
This group has no {memberDisplayTitle.toLocaleLowerCase()}.
|
||||
</Typography>
|
||||
<Typography>{t('membersListCard.noMembersDescription')}</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -273,23 +280,29 @@ export const MembersListCard = (props: {
|
||||
title={`${memberDisplayTitle} (${
|
||||
members?.length || 0
|
||||
}${paginationLabel})`}
|
||||
subheader={`of ${displayName}`}
|
||||
subheader={t('membersListCard.subtitle', {
|
||||
groupName: displayName,
|
||||
})}
|
||||
{...(nbPages <= 1 ? {} : { actions: pagination })}
|
||||
className={classes.root}
|
||||
cardClassName={classes.cardContent}
|
||||
>
|
||||
{showAggregateMembersToggle && (
|
||||
<>
|
||||
Direct Members
|
||||
{t('membersListCard.aggregateMembersToggle.directMembers')}
|
||||
<Switch
|
||||
color="primary"
|
||||
checked={showAggregateMembers}
|
||||
onChange={() => {
|
||||
setShowAggregateMembers(!showAggregateMembers);
|
||||
}}
|
||||
inputProps={{ 'aria-label': 'Users Type Switch' }}
|
||||
inputProps={{
|
||||
'aria-label': t(
|
||||
'membersListCard.aggregateMembersToggle.ariaLabel',
|
||||
),
|
||||
}}
|
||||
/>
|
||||
Aggregated Members
|
||||
{t('membersListCard.aggregateMembersToggle.aggregatedMembers')}
|
||||
</>
|
||||
)}
|
||||
{showAggregateMembers && loadingDescendantMembers ? (
|
||||
|
||||
@@ -26,6 +26,8 @@ import { makeStyles } from '@material-ui/core/styles';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ComponentsGrid } from './ComponentsGrid';
|
||||
import { EntityRelationAggregation } from '../types';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
import { orgTranslationRef } from '../../../translation';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
card: {
|
||||
@@ -83,6 +85,7 @@ export const OwnershipCard = (props: {
|
||||
hideRelationsToggle === undefined ? false : hideRelationsToggle;
|
||||
const classes = useStyles();
|
||||
const { entity } = useEntity();
|
||||
const { t } = useTranslationRef(orgTranslationRef);
|
||||
|
||||
const defaultRelationAggregation =
|
||||
entity.kind === 'User' ? 'aggregated' : 'direct';
|
||||
@@ -98,7 +101,7 @@ export const OwnershipCard = (props: {
|
||||
|
||||
return (
|
||||
<InfoCard
|
||||
title="Ownership"
|
||||
title={t('ownershipCard.title')}
|
||||
variant={variant}
|
||||
className={classes.card}
|
||||
cardClassName={classes.cardContent}
|
||||
@@ -110,13 +113,19 @@ export const OwnershipCard = (props: {
|
||||
<ListItemSecondaryAction
|
||||
className={classes.listItemSecondaryAction}
|
||||
>
|
||||
Direct Relations
|
||||
{t('ownershipCard.aggregateRelationsToggle.directRelations')}
|
||||
<Tooltip
|
||||
placement="top"
|
||||
arrow
|
||||
title={`${
|
||||
getRelationAggregation === 'direct' ? 'Direct' : 'Aggregated'
|
||||
} Relations`}
|
||||
title={
|
||||
getRelationAggregation === 'direct'
|
||||
? t(
|
||||
'ownershipCard.aggregateRelationsToggle.directRelations',
|
||||
)
|
||||
: t(
|
||||
'ownershipCard.aggregateRelationsToggle.aggregatedRelations',
|
||||
)
|
||||
}
|
||||
>
|
||||
<Switch
|
||||
color="primary"
|
||||
@@ -129,10 +138,14 @@ export const OwnershipCard = (props: {
|
||||
setRelationAggregation(updatedRelationAggregation);
|
||||
}}
|
||||
name="pin"
|
||||
inputProps={{ 'aria-label': 'Ownership Type Switch' }}
|
||||
inputProps={{
|
||||
'aria-label': t(
|
||||
'ownershipCard.aggregateRelationsToggle.ariaLabel',
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
Aggregated Relations
|
||||
{t('ownershipCard.aggregateRelationsToggle.aggregatedRelations')}
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
@@ -55,6 +55,8 @@ import { LinksGroup } from '../../Meta';
|
||||
import PersonIcon from '@material-ui/icons/Person';
|
||||
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
import { orgTranslationRef } from '../../../../translation';
|
||||
|
||||
const useStyles = makeStyles((theme: any) => ({
|
||||
closeButton: {
|
||||
@@ -92,6 +94,7 @@ export const UserProfileCard = (props: {
|
||||
const classes = useStyles();
|
||||
const { entity: user } = useEntity<UserEntity>();
|
||||
const [isAllGroupsDialogOpen, setIsAllGroupsDialogOpen] = useState(false);
|
||||
const { t } = useTranslationRef(orgTranslationRef);
|
||||
|
||||
const toggleAllGroupsDialog = useCallback(
|
||||
() =>
|
||||
@@ -102,7 +105,7 @@ export const UserProfileCard = (props: {
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
return <Alert severity="error">User not found</Alert>;
|
||||
return <Alert severity="error">{t('userProfileCard.userNotFound')}</Alert>;
|
||||
}
|
||||
|
||||
const entityMetadataEditUrl =
|
||||
@@ -127,8 +130,8 @@ export const UserProfileCard = (props: {
|
||||
<>
|
||||
{entityMetadataEditUrl && (
|
||||
<IconButton
|
||||
aria-label="Edit"
|
||||
title="Edit Metadata"
|
||||
aria-label={t('userProfileCard.editIconButtonTitle')}
|
||||
title={t('userProfileCard.editIconButtonTitle')}
|
||||
component={Link}
|
||||
to={entityMetadataEditUrl}
|
||||
>
|
||||
@@ -148,7 +151,7 @@ export const UserProfileCard = (props: {
|
||||
{profile?.email && (
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<Tooltip title="Email">
|
||||
<Tooltip title={t('userProfileCard.listItemTitle.email')}>
|
||||
<EmailIcon />
|
||||
</Tooltip>
|
||||
</ListItemIcon>
|
||||
@@ -161,7 +164,7 @@ export const UserProfileCard = (props: {
|
||||
{maxRelations === undefined || maxRelations > 0 ? (
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<Tooltip title="Member of">
|
||||
<Tooltip title={t('userProfileCard.listItemTitle.memberOf')}>
|
||||
<GroupIcon />
|
||||
</Tooltip>
|
||||
</ListItemIcon>
|
||||
@@ -179,9 +182,11 @@ export const UserProfileCard = (props: {
|
||||
onClick={toggleAllGroupsDialog}
|
||||
disableRipple
|
||||
>
|
||||
{` ...More (${
|
||||
memberOfRelations.length - maxRelations
|
||||
})`}
|
||||
{t('userProfileCard.moreGroupButtonTitle', {
|
||||
number: String(
|
||||
memberOfRelations.length - maxRelations,
|
||||
),
|
||||
})}
|
||||
</BaseButton>
|
||||
</>
|
||||
) : null}
|
||||
@@ -203,10 +208,12 @@ export const UserProfileCard = (props: {
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle id="view-all-groups-dialog-title">
|
||||
All {user.metadata.name}'s groups:
|
||||
{t('userProfileCard.allGroupDialog.title', {
|
||||
name: user.metadata.name,
|
||||
})}
|
||||
<IconButton
|
||||
className={classes.closeButton}
|
||||
aria-label="close"
|
||||
aria-label={t('userProfileCard.allGroupDialog.closeButtonTitle')}
|
||||
onClick={toggleAllGroupsDialog}
|
||||
>
|
||||
<CloseIcon />
|
||||
@@ -216,7 +223,9 @@ export const UserProfileCard = (props: {
|
||||
<EntityRefLinks entityRefs={memberOfRelations} defaultKind="Group" />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={toggleAllGroupsDialog}>Close</Button>
|
||||
<Button onClick={toggleAllGroupsDialog}>
|
||||
{t('userProfileCard.allGroupDialog.closeButtonTitle')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</InfoCard>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export const orgTranslationRef = createTranslationRef({
|
||||
id: 'org',
|
||||
messages: {
|
||||
groupProfileCard: {
|
||||
groupNotFound: 'Group not found',
|
||||
editIconButtonTitle: 'Edit Metadata',
|
||||
refreshIconButtonTitle: 'Schedule entity refresh',
|
||||
refreshIconButtonAriaLabel: 'Refresh',
|
||||
listItemTitle: {
|
||||
entityRef: 'Entity Ref',
|
||||
email: 'Email',
|
||||
parentGroup: 'Parent Group',
|
||||
childGroups: 'Child Groups',
|
||||
},
|
||||
},
|
||||
membersListCard: {
|
||||
title: 'Members',
|
||||
subtitle: 'of {{groupName}}',
|
||||
paginationLabel: ', page {{page}} of {{nbPages}}',
|
||||
noMembersDescription: 'This group has no members.',
|
||||
aggregateMembersToggle: {
|
||||
directMembers: 'Direct Members',
|
||||
aggregatedMembers: 'Aggregated Members',
|
||||
ariaLabel: 'Users Type Switch',
|
||||
},
|
||||
},
|
||||
ownershipCard: {
|
||||
title: 'Ownership',
|
||||
aggregateRelationsToggle: {
|
||||
directRelations: 'Direct Relations',
|
||||
aggregatedRelations: 'Aggregated Relations',
|
||||
ariaLabel: 'Ownership Type Switch',
|
||||
},
|
||||
},
|
||||
userProfileCard: {
|
||||
userNotFound: 'User not found',
|
||||
editIconButtonTitle: 'Edit Metadata',
|
||||
listItemTitle: {
|
||||
email: 'Email',
|
||||
memberOf: 'Member of',
|
||||
},
|
||||
moreGroupButtonTitle: '...More ({{number}})',
|
||||
allGroupDialog: {
|
||||
title: "All {{name}}'s groups:",
|
||||
closeButtonTitle: 'Close',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user