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',
+ },
+ },
+ },
+});