diff --git a/.changeset/late-buttons-strive.md b/.changeset/late-buttons-strive.md new file mode 100644 index 0000000000..23e1045ba0 --- /dev/null +++ b/.changeset/late-buttons-strive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Org plugin support i18n diff --git a/plugins/org/report-alpha.api.md b/plugins/org/report-alpha.api.md index 55397c259c..840a5472dd 100644 --- a/plugins/org/report-alpha.api.md +++ b/plugins/org/report-alpha.api.md @@ -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) ``` diff --git a/plugins/org/src/alpha.tsx b/plugins/org/src/alpha.tsx index 7f61fa5865..e5d6aa6675 100644 --- a/plugins/org/src/alpha.tsx +++ b/plugins/org/src/alpha.tsx @@ -98,3 +98,5 @@ export default createFrontendPlugin({ catalogIndex: catalogIndexRouteRef, }), }); + +export { orgTranslationRef } from './translation'; diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index 35eb82334c..5c8757a4dd 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -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 }) => ( @@ -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 Group not found; + return ( + {t('groupProfileCard.groupNotFound')} + ); } const { @@ -111,15 +116,19 @@ export const GroupProfileCard = (props: { const emailHref = profile?.email ? `mailto:${profile.email}` : '#'; const infoCardAction = entityMetadataEditUrl ? ( ) : ( - + ); @@ -133,8 +142,8 @@ export const GroupProfileCard = (props: { <> {allowRefresh && canRefresh && ( @@ -152,31 +161,33 @@ export const GroupProfileCard = (props: { - + {profile?.email && ( - + {profile.email}} - secondary="Email" + secondary={t('groupProfileCard.listItemTitle.email')} /> )} - + @@ -191,12 +202,14 @@ export const GroupProfileCard = (props: { 'N/A' ) } - secondary="Parent Group" + secondary={t('groupProfileCard.listItemTitle.parentGroup')} /> - + @@ -211,7 +224,7 @@ export const GroupProfileCard = (props: { 'N/A' ) } - secondary="Child Groups" + secondary={t('groupProfileCard.listItemTitle.childGroups')} /> {props?.showLinks && } diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx index c051de5ddb..07119ba85b 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx @@ -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 = ( - - This group has no {memberDisplayTitle.toLocaleLowerCase()}. - + {t('membersListCard.noMembersDescription')} ); } @@ -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')} { setShowAggregateMembers(!showAggregateMembers); }} - inputProps={{ 'aria-label': 'Users Type Switch' }} + inputProps={{ + 'aria-label': t( + 'membersListCard.aggregateMembersToggle.ariaLabel', + ), + }} /> - Aggregated Members + {t('membersListCard.aggregateMembersToggle.aggregatedMembers')} )} {showAggregateMembers && loadingDescendantMembers ? ( diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index d179573b09..ff9fd6cec8 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -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 ( - Direct Relations + {t('ownershipCard.aggregateRelationsToggle.directRelations')} - Aggregated Relations + {t('ownershipCard.aggregateRelationsToggle.aggregatedRelations')} diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index 50cb0e1fef..fdfe3fd5c3 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -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(); const [isAllGroupsDialogOpen, setIsAllGroupsDialogOpen] = useState(false); + const { t } = useTranslationRef(orgTranslationRef); const toggleAllGroupsDialog = useCallback( () => @@ -102,7 +105,7 @@ export const UserProfileCard = (props: { ); if (!user) { - return User not found; + return {t('userProfileCard.userNotFound')}; } const entityMetadataEditUrl = @@ -127,8 +130,8 @@ export const UserProfileCard = (props: { <> {entityMetadataEditUrl && ( @@ -148,7 +151,7 @@ export const UserProfileCard = (props: { {profile?.email && ( - + @@ -161,7 +164,7 @@ export const UserProfileCard = (props: { {maxRelations === undefined || maxRelations > 0 ? ( - + @@ -179,9 +182,11 @@ export const UserProfileCard = (props: { onClick={toggleAllGroupsDialog} disableRipple > - {` ...More (${ - memberOfRelations.length - maxRelations - })`} + {t('userProfileCard.moreGroupButtonTitle', { + number: String( + memberOfRelations.length - maxRelations, + ), + })} ) : null} @@ -203,10 +208,12 @@ export const UserProfileCard = (props: { fullWidth > - All {user.metadata.name}'s groups: + {t('userProfileCard.allGroupDialog.title', { + name: user.metadata.name, + })} @@ -216,7 +223,9 @@ export const UserProfileCard = (props: { - + diff --git a/plugins/org/src/translation.ts b/plugins/org/src/translation.ts new file mode 100644 index 0000000000..b7dfda6937 --- /dev/null +++ b/plugins/org/src/translation.ts @@ -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', + }, + }, + }, +});