fix: add missing i18n support for catalog react plugin

Signed-off-by: Eswaraiahsapram <esapram@redhat.com>
This commit is contained in:
Eswaraiahsapram
2025-11-03 16:21:27 +05:30
parent 836b0c7552
commit 36d7582687
10 changed files with 160 additions and 59 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-react': patch
---
Added missing i18n
+25
View File
@@ -45,8 +45,10 @@ export const catalogReactTranslationRef: TranslationRef<
readonly 'entityProcessingStatusPicker.title': 'Processing Status';
readonly 'entityTagPicker.title': 'Tags';
readonly 'entityPeekAheadPopover.title': 'Drill into the entity to see all of the tags.';
readonly 'entityPeekAheadPopover.entityCardActionsAriaLabel': 'Show';
readonly 'entityPeekAheadPopover.entityCardActionsTitle': 'Show details';
readonly 'entityPeekAheadPopover.emailCardAction.title': 'Email {{email}}';
readonly 'entityPeekAheadPopover.emailCardAction.ariaLabel': 'Email';
readonly 'entityPeekAheadPopover.emailCardAction.subTitle': 'mailto {{email}}';
readonly 'entitySearchBar.placeholder': 'Search';
readonly 'entityTypePicker.title': 'Type';
@@ -56,16 +58,34 @@ export const catalogReactTranslationRef: TranslationRef<
readonly 'favoriteEntity.removeFromFavorites': 'Remove from favorites';
readonly 'inspectEntityDialog.title': 'Entity Inspector';
readonly 'inspectEntityDialog.closeButtonTitle': 'Close';
readonly 'inspectEntityDialog.tabsAriaLabel': 'Inspector options';
readonly 'inspectEntityDialog.ancestryPage.title': 'Ancestry';
readonly 'inspectEntityDialog.ancestryPage.descriptionPart1': 'This is the ancestry of entities above the current one - as in, the chain(s) of entities down to the current one, where';
readonly 'inspectEntityDialog.ancestryPage.processorsLink': 'processors emitted';
readonly 'inspectEntityDialog.ancestryPage.descriptionPart2': 'child entities that ultimately led to the current one existing. Note that this is a completely different mechanism from relations.';
readonly 'inspectEntityDialog.colocatedPage.title': 'Colocated';
readonly 'inspectEntityDialog.colocatedPage.description': 'These are the entities that are colocated with this entity - as in, they originated from the same data source (e.g. came from the same YAML file), or from the same origin (e.g. the originally registered URL).';
readonly 'inspectEntityDialog.colocatedPage.alertNoLocation': 'Entity had no location information.';
readonly 'inspectEntityDialog.colocatedPage.alertNoEntity': 'There were no other entities on this location.';
readonly 'inspectEntityDialog.colocatedPage.locationHeader': 'At the same location';
readonly 'inspectEntityDialog.colocatedPage.originHeader': 'At the same origin';
readonly 'inspectEntityDialog.jsonPage.title': 'Entity as JSON';
readonly 'inspectEntityDialog.jsonPage.description': 'This is the raw entity data as received from the catalog, on JSON form.';
readonly 'inspectEntityDialog.overviewPage.title': 'Overview';
readonly 'inspectEntityDialog.overviewPage.labels': 'Labels';
readonly 'inspectEntityDialog.overviewPage.annotations': 'Annotations';
readonly 'inspectEntityDialog.overviewPage.tags': 'Tags';
readonly 'inspectEntityDialog.overviewPage.relationTitle': 'Relations';
readonly 'inspectEntityDialog.overviewPage.statusTitle': 'Status';
readonly 'inspectEntityDialog.overviewPage.identityTitle': 'Identity';
readonly 'inspectEntityDialog.overviewPage.metadataTitle': 'Metadata';
readonly 'inspectEntityDialog.yamlPage.title': 'Entity as YAML';
readonly 'inspectEntityDialog.yamlPage.description': 'This is the raw entity data as received from the catalog, on YAML form.';
readonly 'inspectEntityDialog.tabNames.json': 'Raw JSON';
readonly 'inspectEntityDialog.tabNames.yaml': 'Raw YAML';
readonly 'inspectEntityDialog.tabNames.overview': 'Overview';
readonly 'inspectEntityDialog.tabNames.ancestry': 'Ancestry';
readonly 'inspectEntityDialog.tabNames.colocated': 'Colocated';
readonly 'unregisterEntityDialog.title': 'Are you sure you want to unregister this entity?';
readonly 'unregisterEntityDialog.cancelButtonTitle': 'Cancel';
readonly 'unregisterEntityDialog.deleteButtonTitle': 'Delete Entity';
@@ -98,6 +118,11 @@ export const catalogReactTranslationRef: TranslationRef<
readonly 'entityTableColumnTitle.lifecycle': 'Lifecycle';
readonly 'entityTableColumnTitle.owner': 'Owner';
readonly 'entityTableColumnTitle.targets': 'Targets';
readonly 'missingAnnotationEmptyState.title': 'Missing Annotation';
readonly 'missingAnnotationEmptyState.readMore': 'Read more';
readonly 'missingAnnotationEmptyState.annotationYaml': 'Add the annotation to your {{entityKind}} YAML as shown in the highlighted example below:';
readonly 'missingAnnotationEmptyState.generateDescription.multiple': 'The annotations {{annotations}} are missing. You need to add the annotations to your {{entityKind}} if you want to enable this tool.';
readonly 'missingAnnotationEmptyState.generateDescription.single': 'The annotation {{annotations}} is missing. You need to add the annotation to your {{entityKind}} if you want to enable this tool.';
}
>;
@@ -30,7 +30,7 @@ export const EmailCardAction = (props: { email: string }) => {
return (
<IconButton
component={Link}
aria-label="Email"
aria-label={t('entityPeekAheadPopover.emailCardAction.ariaLabel')}
title={t('entityPeekAheadPopover.emailCardAction.title', {
email: props.email,
})}
@@ -35,7 +35,7 @@ export const EntityCardActions = (props: { entity: Entity }) => {
return (
<IconButton
component={Link}
aria-label="Show"
aria-label={t('entityPeekAheadPopover.entityCardActionsAriaLabel')}
title={t('entityPeekAheadPopover.entityCardActionsTitle')}
to={entityRoute(getCompoundEntityRef(props.entity))}
>
@@ -24,7 +24,7 @@ import DialogTitle from '@material-ui/core/DialogTitle';
import Tab from '@material-ui/core/Tab';
import Tabs from '@material-ui/core/Tabs';
import { makeStyles } from '@material-ui/core/styles';
import { ComponentProps, useEffect, useState, ReactNode } from 'react';
import { ComponentProps, useEffect, useState, ReactNode, useMemo } from 'react';
import { AncestryPage } from './components/AncestryPage';
import { ColocatedPage } from './components/ColocatedPage';
import { JsonPage } from './components/JsonPage';
@@ -85,18 +85,12 @@ function a11yProps(index: number) {
};
}
const tabNames: Record<
type TabKey = 'overview' | 'ancestry' | 'colocated' | 'json' | 'yaml';
type TabNames = Record<
NonNullable<ComponentProps<typeof InspectEntityDialog>['initialTab']>,
string
> = {
overview: 'Overview',
ancestry: 'Ancestry',
colocated: 'Colocated',
json: 'Raw JSON',
yaml: 'Raw YAML',
} as const;
const tabs = Object.keys(tabNames) as Array<keyof typeof tabNames>;
>;
/**
* A dialog that lets users inspect the low level details of their entities.
@@ -106,20 +100,33 @@ const tabs = Object.keys(tabNames) as Array<keyof typeof tabNames>;
export function InspectEntityDialog(props: {
open: boolean;
entity: Entity;
initialTab?: 'overview' | 'ancestry' | 'colocated' | 'json' | 'yaml';
initialTab?: TabKey;
onClose: () => void;
onSelect?: (tab: string) => void;
}) {
const classes = useStyles();
const { t } = useTranslationRef(catalogReactTranslationRef);
const tabNames: TabNames = useMemo(
() => ({
overview: t('inspectEntityDialog.tabNames.overview'),
ancestry: t('inspectEntityDialog.tabNames.ancestry'),
colocated: t('inspectEntityDialog.tabNames.colocated'),
json: t('inspectEntityDialog.tabNames.json'),
yaml: t('inspectEntityDialog.tabNames.yaml'),
}),
[t],
);
const tabs = Object.keys(tabNames) as TabKey[];
const [activeTab, setActiveTab] = useState(
getTabIndex(tabs, props.initialTab),
);
const { t } = useTranslationRef(catalogReactTranslationRef);
useEffect(() => {
getTabIndex(tabs, props.initialTab);
}, [props.open, props.initialTab]);
}, [props.open, props.initialTab, tabs]);
if (!props.entity) {
return null;
@@ -147,7 +154,7 @@ export function InspectEntityDialog(props: {
setActiveTab(tabIndex);
props.onSelect?.(tabs[tabIndex]);
}}
aria-label="Inspector options"
aria-label={t('inspectEntityDialog.tabsAriaLabel')}
className={classes.tabs}
>
{tabs.map((tab, index) => (
@@ -181,9 +188,6 @@ export function InspectEntityDialog(props: {
);
}
function getTabIndex(
allTabs: string[],
initialTab: keyof typeof tabNames | undefined,
) {
function getTabIndex(allTabs: string[], initialTab: TabKey | undefined) {
return initialTab ? allTabs.indexOf(initialTab) : 0;
}
@@ -213,13 +213,13 @@ export function AncestryPage(props: { entity: Entity }) {
{t('inspectEntityDialog.ancestryPage.title')}
</DialogContentText>
<DialogContentText gutterBottom>
This is the ancestry of entities above the current one - as in, the
chain(s) of entities down to the current one, where{' '}
<Link to="https://backstage.io/docs/features/software-catalog/life-of-an-entity">
processors emitted
</Link>{' '}
child entities that ultimately led to the current one existing. Note
that this is a completely different mechanism from relations.
{t('inspectEntityDialog.ancestryPage.description', {
processorsLink: (
<Link to="https://backstage.io/docs/features/software-catalog/life-of-an-entity">
{t('inspectEntityDialog.ancestryPage.processorsLink')}
</Link>
),
})}
</DialogContentText>
<Box mt={4}>
<DependencyGraph
@@ -137,13 +137,19 @@ function Contents(props: { entity: Entity }) {
{atLocation.length > 0 && (
<EntityList
entities={atLocation}
header={['At the same location', location!]}
header={[
t('inspectEntityDialog.colocatedPage.locationHeader'),
location!,
]}
/>
)}
{atOrigin.length > 0 && (
<EntityList
entities={atOrigin}
header={['At the same origin', originLocation!]}
header={[
t('inspectEntityDialog.colocatedPage.originHeader'),
originLocation!,
]}
/>
)}
</>
@@ -69,7 +69,7 @@ export function OverviewPage(props: { entity: AlphaEntity }) {
{t('inspectEntityDialog.overviewPage.title')}
</DialogContentText>
<div className={classes.root}>
<Container title="Identity">
<Container title={t('inspectEntityDialog.overviewPage.identity.title')}>
<List dense>
<ListItem>
<ListItemText primary="apiVersion" secondary={apiVersion} />
@@ -110,13 +110,13 @@ export function OverviewPage(props: { entity: AlphaEntity }) {
</List>
</Container>
<Container title="Metadata">
<Container title={t('inspectEntityDialog.overviewPage.metadata.title')}>
{!!Object.keys(metadata.annotations || {}).length && (
<List
dense
subheader={
<ListSubheader>
Annotations
{t('inspectEntityDialog.overviewPage.annotations')}
<HelpIcon to="https://backstage.io/docs/features/software-catalog/well-known-annotations" />
</ListSubheader>
}
@@ -127,14 +127,28 @@ export function OverviewPage(props: { entity: AlphaEntity }) {
</List>
)}
{!!Object.keys(metadata.labels || {}).length && (
<List dense subheader={<ListSubheader>Labels</ListSubheader>}>
<List
dense
subheader={
<ListSubheader>
{t('inspectEntityDialog.overviewPage.labels')}
</ListSubheader>
}
>
{Object.entries(metadata.labels!).map(entry => (
<KeyValueListItem key={entry[0]} indent entry={entry} />
))}
</List>
)}
{!!metadata.tags?.length && (
<List dense subheader={<ListSubheader>Tags</ListSubheader>}>
<List
dense
subheader={
<ListSubheader>
{t('inspectEntityDialog.overviewPage.tags')}
</ListSubheader>
}
>
{metadata.tags.map((tag, index) => (
<ListItem key={`${tag}-${index}`}>
<ListItemIcon />
@@ -147,7 +161,7 @@ export function OverviewPage(props: { entity: AlphaEntity }) {
{!!relations.length && (
<Container
title="Relations"
title={t('inspectEntityDialog.overviewPage.relation.title')}
helpLink="https://backstage.io/docs/features/software-catalog/well-known-relations"
>
{Object.entries(groupedRelations).map(
@@ -172,7 +186,7 @@ export function OverviewPage(props: { entity: AlphaEntity }) {
{!!status.items?.length && (
<Container
title="Status"
title={t('inspectEntityDialog.overviewPage.status.title')}
helpLink="https://backstage.io/docs/features/software-catalog/well-known-statuses"
>
{status.items.map((item, index) => (
@@ -20,7 +20,12 @@ import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import { CodeSnippet, Link, EmptyState } from '@backstage/core-components';
import { Entity } from '@backstage/catalog-model';
import {
TranslationFunction,
useTranslationRef,
} from '@backstage/core-plugin-api/alpha';
import { useEntity } from '../../hooks';
import { catalogReactTranslationRef } from '../../translation';
/** @public */
export type MissingAnnotationEmptyStateClassKey = 'code';
@@ -68,23 +73,24 @@ spec:
};
}
function generateDescription(annotations: string[], entityKind = 'Component') {
const isSingular = annotations.length <= 1;
return (
<>
The {isSingular ? 'annotation' : 'annotations'}{' '}
{annotations
.map(ann => <code>{ann}</code>)
.reduce((prev, curr) => (
<>
{prev}, {curr}
</>
))}{' '}
{isSingular ? 'is' : 'are'} missing. You need to add the{' '}
{isSingular ? 'annotation' : 'annotations'} to your {entityKind} if you
want to enable this tool.
</>
);
function generateDescription(
annotations: string[],
entityKind = 'Component',
t: TranslationFunction<typeof catalogReactTranslationRef.T>,
) {
const annotationList = annotations
.map(ann => <code key={ann}>{ann}</code>)
.reduce((prev, curr) => (
<>
{prev}, {curr}
</>
));
return t('missingAnnotationEmptyState.generateDescription', {
count: annotations.length,
entityKind,
annotations: annotationList,
});
}
/**
@@ -95,6 +101,8 @@ export function MissingAnnotationEmptyState(props: {
annotation: string | string[];
readMoreUrl?: string;
}) {
const { t } = useTranslationRef(catalogReactTranslationRef);
let entity: Entity | undefined;
try {
const entityContext = useEntity();
@@ -115,13 +123,12 @@ export function MissingAnnotationEmptyState(props: {
return (
<EmptyState
missing="field"
title="Missing Annotation"
description={generateDescription(annotations, entityKind)}
title={t('missingAnnotationEmptyState.title')}
description={generateDescription(annotations, entityKind, t)}
action={
<>
<Typography variant="body1">
Add the annotation to your {entityKind} YAML as shown in the
highlighted example below:
{t('missingAnnotationEmptyState.annotationYaml', { entityKind })}
</Typography>
<Box className={classes.code}>
<CodeSnippet
@@ -133,7 +140,7 @@ export function MissingAnnotationEmptyState(props: {
/>
</Box>
<Button color="primary" component={Link} to={url}>
Read more
{t('missingAnnotationEmptyState.readMore')}
</Button>
</>
}
+40
View File
@@ -48,7 +48,9 @@ export const catalogReactTranslationRef = createTranslationRef({
emailCardAction: {
title: 'Email {{email}}',
subTitle: 'mailto {{email}}',
ariaLabel: 'Email',
},
entityCardActionsAriaLabel: 'Show',
entityCardActionsTitle: 'Show details',
},
entitySearchBar: {
@@ -68,6 +70,9 @@ export const catalogReactTranslationRef = createTranslationRef({
closeButtonTitle: 'Close',
ancestryPage: {
title: 'Ancestry',
description:
'This is the ancestry of entities above the current one - as in, the chain(s) of entities down to the current one, where {{processorsLink}} child entities that ultimately led to the current one existing. Note that this is a completely different mechanism from relations.',
processorsLink: 'processors emitted',
},
colocatedPage: {
title: 'Colocated',
@@ -75,6 +80,8 @@ export const catalogReactTranslationRef = createTranslationRef({
'These are the entities that are colocated with this entity - as in, they originated from the same data source (e.g. came from the same YAML file), or from the same origin (e.g. the originally registered URL).',
alertNoLocation: 'Entity had no location information.',
alertNoEntity: 'There were no other entities on this location.',
locationHeader: 'At the same location',
originHeader: 'At the same origin',
},
jsonPage: {
title: 'Entity as JSON',
@@ -83,12 +90,35 @@ export const catalogReactTranslationRef = createTranslationRef({
},
overviewPage: {
title: 'Overview',
relation: {
title: 'Relations',
},
status: {
title: 'Status',
},
identity: {
title: 'Identity',
},
metadata: {
title: 'Metadata',
},
annotations: 'Annotations',
labels: 'Labels',
tags: 'Tags',
},
yamlPage: {
title: 'Entity as YAML',
description:
'This is the raw entity data as received from the catalog, on YAML form.',
},
tabNames: {
overview: 'Overview',
ancestry: 'Ancestry',
colocated: 'Colocated',
json: 'Raw JSON',
yaml: 'Raw YAML',
},
tabsAriaLabel: 'Inspector options',
},
unregisterEntityDialog: {
title: 'Are you sure you want to unregister this entity?',
@@ -138,5 +168,15 @@ export const catalogReactTranslationRef = createTranslationRef({
label: 'Label',
domain: 'Domain',
},
missingAnnotationEmptyState: {
title: 'Missing Annotation',
readMore: 'Read more',
annotationYaml:
'Add the annotation to your {{entityKind}} YAML as shown in the highlighted example below:',
generateDescription_one:
'The annotation {{annotations}} is missing. You need to add the annotation to your {{entityKind}} if you want to enable this tool.',
generateDescription_other:
'The annotations {{annotations}} are missing. You need to add the annotations to your {{entityKind}} if you want to enable this tool.',
},
},
});