feat: add i18n support for notifications plugin (#31558)

* feat: add i18n support for notifications plugin

Signed-off-by: Yi Cai <yicai@redhat.com>

* added changeset

Signed-off-by: Yi Cai <yicai@redhat.com>

* updates

Signed-off-by: Yi Cai <yicai@redhat.com>

* fix failed e2e checks

Signed-off-by: Yi Cai <yicai@redhat.com>

* resolved review comments

Signed-off-by: Yi Cai <yicai@redhat.com>

* fixed some untranslated string issue

Signed-off-by: Yi Cai <yicai@redhat.com>

* updated report-alpha.api.md

Signed-off-by: Yi Cai <yicai@redhat.com>

* updated report api

Signed-off-by: Yi Cai <yicai@redhat.com>

* updated report api

Signed-off-by: Yi Cai <yicai@redhat.com>

* simplified code

Signed-off-by: Yi Cai <yicai@redhat.com>

* updated import to use new dependency

Signed-off-by: Yi Cai <yicai@redhat.com>

* updated report-alpha.api.md

Signed-off-by: Yi Cai <yicai@redhat.com>

* code clean up

Signed-off-by: Yi Cai <yicai@redhat.com>

* addressed review comment

Signed-off-by: Yi Cai <yicai@redhat.com>

---------

Signed-off-by: Yi Cai <yicai@redhat.com>
This commit is contained in:
Yi C
2026-01-20 08:56:47 -05:00
committed by GitHub
parent 2545b53932
commit 4452d15efa
14 changed files with 330 additions and 90 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-notifications': patch
---
Added i18n support.
+3 -3
View File
@@ -1,5 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
-2
View File
@@ -1,3 +1 @@
# Knip report
+66
View File
@@ -13,6 +13,7 @@ import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api';
import { RouteRef } from '@backstage/core-plugin-api';
import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api';
import { TranslationRef } from '@backstage/frontend-plugin-api';
// @alpha (undocumented)
const _default: OverridableFrontendPlugin<
@@ -67,5 +68,70 @@ const _default: OverridableFrontendPlugin<
>;
export default _default;
// @alpha (undocumented)
export const notificationsTranslationRef: TranslationRef<
'plugin.notifications',
{
readonly 'table.errors.markAllReadFailed': 'Failed to mark all notifications as read';
readonly 'table.pagination.firstTooltip': 'First Page';
readonly 'table.pagination.labelDisplayedRows': '{from}-{to} of {count}';
readonly 'table.pagination.labelRowsSelect': 'rows';
readonly 'table.pagination.lastTooltip': 'Last Page';
readonly 'table.pagination.nextTooltip': 'Next Page';
readonly 'table.pagination.previousTooltip': 'Previous Page';
readonly 'table.emptyMessage': 'No records to display';
readonly 'table.bulkActions.markAllRead': 'Mark all read';
readonly 'table.bulkActions.markSelectedAsRead': 'Mark selected as read';
readonly 'table.bulkActions.returnSelectedAmongUnread': 'Return selected among unread';
readonly 'table.bulkActions.saveSelectedForLater': 'Save selected for later';
readonly 'table.bulkActions.undoSaveForSelected': 'Undo save for selected';
readonly 'table.confirmDialog.title': 'Are you sure?';
readonly 'table.confirmDialog.markAllReadDescription': 'Mark <b>all</b> notifications as <b>read</b>.';
readonly 'table.confirmDialog.markAllReadConfirmation': 'Mark All';
readonly 'filters.view.all': 'All';
readonly 'filters.view.label': 'View';
readonly 'filters.view.read': 'Read notifications';
readonly 'filters.view.saved': 'Saved';
readonly 'filters.view.unread': 'Unread notifications';
readonly 'filters.title': 'Filters';
readonly 'filters.severity.normal': 'Normal';
readonly 'filters.severity.high': 'High';
readonly 'filters.severity.low': 'Low';
readonly 'filters.severity.label': 'Min severity';
readonly 'filters.severity.critical': 'Critical';
readonly 'filters.topic.label': 'Topic';
readonly 'filters.topic.anyTopic': 'Any topic';
readonly 'filters.createdAfter.label': 'Sent out';
readonly 'filters.createdAfter.placeholder': 'Notifications since';
readonly 'filters.createdAfter.last24h': 'Last 24h';
readonly 'filters.createdAfter.lastWeek': 'Last week';
readonly 'filters.createdAfter.anyTime': 'Any time';
readonly 'filters.sortBy.origin': 'Origin';
readonly 'filters.sortBy.label': 'Sort by';
readonly 'filters.sortBy.placeholder': 'Field to sort by';
readonly 'filters.sortBy.newest': 'Newest on top';
readonly 'filters.sortBy.oldest': 'Oldest on top';
readonly 'filters.sortBy.topic': 'Topic';
readonly 'settings.table.origin': 'Origin';
readonly 'settings.table.topic': 'Topic';
readonly 'settings.title': 'Notification settings';
readonly 'settings.errors.useNotificationFormat': 'useNotificationFormat must be used within a NotificationFormatProvider';
readonly 'settings.errorTitle': 'Failed to load settings';
readonly 'settings.noSettingsAvailable': 'No notification settings available, check back later';
readonly 'sidebar.title': 'Notifications';
readonly 'sidebar.errors.markAsReadFailed': 'Failed to mark notification as read';
readonly 'sidebar.errors.fetchNotificationFailed': 'Failed to fetch notification';
readonly 'notificationsPage.title': 'Notifications';
readonly 'notificationsPage.tableTitle.all_one': 'All notifications ({{count}})';
readonly 'notificationsPage.tableTitle.all_other': 'All notifications ({{count}})';
readonly 'notificationsPage.tableTitle.saved_one': 'Saved notifications ({{count}})';
readonly 'notificationsPage.tableTitle.saved_other': 'Saved notifications ({{count}})';
readonly 'notificationsPage.tableTitle.unread_one': 'Unread notifications ({{count}})';
readonly 'notificationsPage.tableTitle.unread_other': 'Unread notifications ({{count}})';
readonly 'notificationsPage.tableTitle.read_one': 'Read notifications ({{count}})';
readonly 'notificationsPage.tableTitle.read_other': 'Read notifications ({{count}})';
}
>;
// (No @packageDocumentation comment for this package)
```
+3 -4
View File
@@ -13,7 +13,6 @@ import { Notification as Notification_2 } from '@backstage/plugin-notifications-
import { NotificationSettings } from '@backstage/plugin-notifications-common';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
import { NotificationStatus } from '@backstage/plugin-notifications-common';
import * as React_2 from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
import { TableProps } from '@backstage/core-components';
@@ -111,10 +110,10 @@ export type NotificationSnackbarProperties = {
};
dense?: boolean;
maxSnack?: number;
snackStyle?: React_2.CSSProperties;
iconVariant?: Partial<Record<NotificationSeverity, React_2.ReactNode>>;
snackStyle?: React.CSSProperties;
iconVariant?: Partial<Record<NotificationSeverity, React.ReactNode>>;
Components?: {
[key in NotificationSeverity]: React_2.JSXElementConstructor<any>;
[key in NotificationSeverity]: React.JSXElementConstructor<any>;
};
};
+2
View File
@@ -55,3 +55,5 @@ export default createFrontendPlugin({
// TODO(Rugvip): Nav item (i.e. NotificationsSidebarItem) currently needs to be installed manually
extensions: [page, api],
});
export { notificationsTranslationRef } from './translation';
@@ -15,10 +15,14 @@
*/
import { ChangeEvent } from 'react';
import FormControl from '@material-ui/core/FormControl';
import Divider from '@material-ui/core/Divider';
import Grid from '@material-ui/core/Grid';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import Select from '@material-ui/core/Select';
import Typography from '@material-ui/core/Typography';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { notificationsTranslationRef } from '../../translation';
import { GetNotificationsOptions } from '../../api';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
@@ -44,58 +48,53 @@ export type NotificationsFiltersProps = {
const ALL = '___all___';
export const CreatedAfterOptions: {
[key: string]: { label: string; getDate: () => Date };
} = {
type TranslationKey = keyof (typeof notificationsTranslationRef)['T'];
export const CreatedAfterOptions = {
last24h: {
label: 'Last 24h',
labelKey: 'filters.createdAfter.last24h' satisfies TranslationKey,
getDate: () => new Date(Date.now() - 24 * 3600 * 1000),
},
lastWeek: {
label: 'Last week',
labelKey: 'filters.createdAfter.lastWeek' satisfies TranslationKey,
getDate: () => new Date(Date.now() - 7 * 24 * 3600 * 1000),
},
all: {
label: 'Any time',
labelKey: 'filters.createdAfter.anyTime' satisfies TranslationKey,
getDate: () => new Date(0),
},
};
} as const;
export const SortByOptions: {
[key: string]: {
label: string;
sortBy: SortBy;
};
} = {
export const SortByOptions = {
newest: {
label: 'Newest on top',
labelKey: 'filters.sortBy.newest' satisfies TranslationKey,
sortBy: {
sort: 'created',
sortOrder: 'desc',
sort: 'created' as const,
sortOrder: 'desc' as const,
},
},
oldest: {
label: 'Oldest on top',
labelKey: 'filters.sortBy.oldest' satisfies TranslationKey,
sortBy: {
sort: 'created',
sortOrder: 'asc',
sort: 'created' as const,
sortOrder: 'asc' as const,
},
},
topic: {
label: 'Topic',
labelKey: 'filters.sortBy.topic' satisfies TranslationKey,
sortBy: {
sort: 'topic',
sortOrder: 'asc',
sort: 'topic' as const,
sortOrder: 'asc' as const,
},
},
origin: {
label: 'Origin',
labelKey: 'filters.sortBy.origin' satisfies TranslationKey,
sortBy: {
sort: 'origin',
sortOrder: 'asc',
sort: 'origin' as const,
sortOrder: 'asc' as const,
},
},
};
} as const;
const getSortByText = (sortBy?: SortBy): string => {
if (sortBy?.sort === 'created' && sortBy?.sortOrder === 'asc') {
@@ -111,13 +110,6 @@ const getSortByText = (sortBy?: SortBy): string => {
return 'newest';
};
const AllSeverityOptions: { [key in NotificationSeverity]: string } = {
critical: 'Critical',
high: 'High',
normal: 'Normal',
low: 'Low',
};
export const NotificationsFilters = ({
sorting,
onSortingChanged,
@@ -133,6 +125,7 @@ export const NotificationsFilters = ({
onTopicChanged,
allTopics,
}: NotificationsFiltersProps) => {
const { t } = useTranslationRef(notificationsTranslationRef);
const sortByText = getSortByText(sorting);
const handleOnCreatedAfterChanged = (
@@ -163,7 +156,8 @@ export const NotificationsFilters = ({
const handleOnSortByChanged = (
event: ChangeEvent<{ name?: string; value: unknown }>,
) => {
const idx = (event.target.value as string) || 'newest';
const idx = ((event.target.value as string) ||
'newest') as keyof typeof SortByOptions;
const option = SortByOptions[idx];
onSortingChanged({ ...option.sortBy });
};
@@ -197,37 +191,49 @@ export const NotificationsFilters = ({
return (
<>
<Grid container>
<Grid item xs={12}>
<Typography variant="h6">{t('filters.title')}</Typography>
<Divider variant="fullWidth" />
</Grid>
<Grid item xs={12}>
<FormControl fullWidth variant="outlined" size="small">
<InputLabel id="notifications-filter-view">View</InputLabel>
<InputLabel id="notifications-filter-view">
{t('filters.view.label')}
</InputLabel>
<Select
labelId="notifications-filter-view"
label="View"
label={t('filters.view.label')}
value={viewValue}
onChange={handleOnViewChanged}
>
<MenuItem value="unread">Unread notifications</MenuItem>
<MenuItem value="read">Read notifications</MenuItem>
<MenuItem value="saved">Saved</MenuItem>
<MenuItem value="all">All</MenuItem>
<MenuItem value="unread">{t('filters.view.unread')}</MenuItem>
<MenuItem value="read">{t('filters.view.read')}</MenuItem>
<MenuItem value="saved">{t('filters.view.saved')}</MenuItem>
<MenuItem value="all">{t('filters.view.all')}</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth variant="outlined" size="small">
<InputLabel id="notifications-filter-created">Sent out</InputLabel>
<InputLabel id="notifications-filter-created">
{t('filters.createdAfter.label')}
</InputLabel>
<Select
label="Sent out"
label={t('filters.createdAfter.label')}
labelId="notifications-filter-created"
placeholder="Notifications since"
placeholder={t('filters.createdAfter.placeholder')}
value={createdAfter}
onChange={handleOnCreatedAfterChanged}
>
{Object.keys(CreatedAfterOptions).map((key: string) => (
<MenuItem value={key} key={key}>
{CreatedAfterOptions[key].label}
{t(
CreatedAfterOptions[key as keyof typeof CreatedAfterOptions]
.labelKey,
)}
</MenuItem>
))}
</Select>
@@ -236,18 +242,20 @@ export const NotificationsFilters = ({
<Grid item xs={12}>
<FormControl fullWidth variant="outlined" size="small">
<InputLabel id="notifications-filter-sort">Sort by</InputLabel>
<InputLabel id="notifications-filter-sort">
{t('filters.sortBy.label')}
</InputLabel>
<Select
label="Sort by"
label={t('filters.sortBy.label')}
labelId="notifications-filter-sort"
placeholder="Field to sort by"
placeholder={t('filters.sortBy.placeholder')}
value={sortByText}
onChange={handleOnSortByChanged}
>
{Object.keys(SortByOptions).map((key: string) => (
<MenuItem value={key} key={key}>
{SortByOptions[key].label}
{t(SortByOptions[key as keyof typeof SortByOptions].labelKey)}
</MenuItem>
))}
</Select>
@@ -257,18 +265,20 @@ export const NotificationsFilters = ({
<Grid item xs={12}>
<FormControl fullWidth variant="outlined" size="small">
<InputLabel id="notifications-filter-severity">
Min severity
{t('filters.severity.label')}
</InputLabel>
<Select
label="Min severity"
label={t('filters.severity.label')}
labelId="notifications-filter-severity"
value={severity}
onChange={handleOnSeverityChanged}
>
{Object.keys(AllSeverityOptions).map((key: string) => (
{(
['critical', 'high', 'normal', 'low'] as NotificationSeverity[]
).map(key => (
<MenuItem value={key} key={key}>
{AllSeverityOptions[key as NotificationSeverity]}
{t(`filters.severity.${key}`)}
</MenuItem>
))}
</Select>
@@ -277,16 +287,18 @@ export const NotificationsFilters = ({
<Grid item xs={12}>
<FormControl fullWidth variant="outlined" size="small">
<InputLabel id="notifications-filter-topic">Topic</InputLabel>
<InputLabel id="notifications-filter-topic">
{t('filters.topic.label')}
</InputLabel>
<Select
label="Topic"
label={t('filters.topic.label')}
labelId="notifications-filter-topic"
value={topic ?? ALL}
onChange={handleOnTopicChanged}
>
<MenuItem value={ALL} key={ALL}>
Any topic
{t('filters.topic.anyTopic')}
</MenuItem>
{sortedAllTopics.map((item: string) => (
@@ -24,6 +24,15 @@ import {
import Grid from '@material-ui/core/Grid';
import { ConfirmProvider } from 'material-ui-confirm';
import { useSignal } from '@backstage/plugin-signals-react';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { notificationsTranslationRef } from '../../translation';
const TableTitleKeys = {
all: 'notificationsPage.tableTitle.all',
saved: 'notificationsPage.tableTitle.saved',
unread: 'notificationsPage.tableTitle.unread',
read: 'notificationsPage.tableTitle.read',
} as const;
import { NotificationsTable } from '../NotificationsTable';
import { useNotificationsApi } from '../../hooks';
@@ -58,8 +67,9 @@ export type NotificationsPageProps = {
};
export const NotificationsPage = (props?: NotificationsPageProps) => {
const { t } = useTranslationRef(notificationsTranslationRef);
const {
title = 'Notifications',
title = t('notificationsPage.title'),
themeId = 'tool',
subtitle,
tooltip,
@@ -101,7 +111,10 @@ export const NotificationsPage = (props?: NotificationsPageProps) => {
options.topic = topic;
}
const createdAfterDate = CreatedAfterOptions[createdAfter].getDate();
const createdAfterDate =
CreatedAfterOptions[
createdAfter as keyof typeof CreatedAfterOptions
].getDate();
if (createdAfterDate.valueOf() > 0) {
options.createdAfter = createdAfterDate;
}
@@ -156,17 +169,21 @@ export const NotificationsPage = (props?: NotificationsPageProps) => {
const isUnread = !!value?.[1]?.unread;
const allTopics = value?.[2]?.topics;
let tableTitle = `All notifications `;
let tableTitle: string = t(TableTitleKeys.all, {
count: totalCount ?? 0,
});
if (saved) {
tableTitle = `Saved notifications`;
tableTitle = t(TableTitleKeys.saved, {
count: totalCount ?? 0,
});
} else if (unreadOnly === true) {
tableTitle = `Unread notifications`;
tableTitle = t(TableTitleKeys.unread, {
count: totalCount ?? 0,
});
} else if (unreadOnly === false) {
tableTitle = `Read notifications`;
}
if (totalCount) {
tableTitle += ` (${totalCount})`;
tableTitle = t(TableTitleKeys.read, {
count: totalCount ?? 0,
});
}
return (
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useNotificationsApi } from '../../hooks';
import { Link, SidebarItem } from '@backstage/core-components';
@@ -22,6 +22,8 @@ import MarkAsReadIcon from '@material-ui/icons/CheckCircle';
import MarkAsUnsavedIcon from '@material-ui/icons/LabelOff' /* TODO: use BookmarkRemove and BookmarkAdd once we have mui 5 icons */;
import MarkAsSavedIcon from '@material-ui/icons/Label';
import MarkAllReadIcon from '@material-ui/icons/DoneAll';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { notificationsTranslationRef } from '../../translation';
export const BulkActions = ({
selectedNotifications,
@@ -38,6 +40,7 @@ export const BulkActions = ({
onSwitchSavedStatus: (ids: Notification['id'][], newStatus: boolean) => void;
onMarkAllRead?: () => void;
}) => {
const { t } = useTranslationRef(notificationsTranslationRef);
const isDisabled = selectedNotifications.size === 0;
const bulkNotifications = notifications.filter(notification =>
selectedNotifications.has(notification.id),
@@ -51,24 +54,25 @@ export const BulkActions = ({
);
const markAsReadText = isOneRead
? 'Return selected among unread'
: 'Mark selected as read';
? t('table.bulkActions.returnSelectedAmongUnread')
: t('table.bulkActions.markSelectedAsRead');
const IconComponent = isOneRead ? MarkAsUnreadIcon : MarkAsReadIcon;
const markAsSavedText = isOneSaved
? 'Undo save for selected'
: 'Save selected for later';
? t('table.bulkActions.undoSaveForSelected')
: t('table.bulkActions.saveSelectedForLater');
const SavedIconComponent = isOneSaved ? MarkAsUnsavedIcon : MarkAsSavedIcon;
const markAllReadText = t('table.bulkActions.markAllRead');
return (
<Grid container wrap="nowrap">
<Grid item xs={3}>
{onMarkAllRead ? (
<Tooltip title="Mark all read">
<Tooltip title={markAllReadText}>
<div>
{/* The <div> here is a workaround for the Tooltip which does not work for a "disabled" child */}
<IconButton disabled={!isUnread} onClick={onMarkAllRead}>
<MarkAllReadIcon aria-label={markAsSavedText} />
<MarkAllReadIcon aria-label={markAllReadText} />
</IconButton>
</div>
</Tooltip>
@@ -32,6 +32,8 @@ import {
TableColumn,
TableProps,
} from '@backstage/core-components';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { notificationsTranslationRef } from '../../translation';
import { notificationsApiRef } from '../../api';
import { SelectAll } from './SelectAll';
@@ -86,6 +88,7 @@ export const NotificationsTable = ({
pageSize,
totalCount,
}: NotificationsTableProps) => {
const { t } = useTranslationRef(notificationsTranslationRef);
const classes = useStyles();
const notificationsApi = useApi(notificationsApiRef);
const alertApi = useApi(alertApiRef);
@@ -341,6 +344,19 @@ export const NotificationsTable = ({
onSearchChange={throttledContainsTextHandler}
data={notifications}
columns={compactColumns}
localization={{
body: {
emptyDataSourceMessage: t('table.emptyMessage'),
},
pagination: {
firstTooltip: t('table.pagination.firstTooltip'),
labelDisplayedRows: t('table.pagination.labelDisplayedRows'),
labelRowsSelect: t('table.pagination.labelRowsSelect'),
lastTooltip: t('table.pagination.lastTooltip'),
nextTooltip: t('table.pagination.nextTooltip'),
previousTooltip: t('table.pagination.previousTooltip'),
},
}}
/>
);
};
@@ -20,6 +20,8 @@ import { useNotificationsApi } from '../../hooks';
import { NotificationSettings } from '@backstage/plugin-notifications-common';
import { notificationsApiRef } from '../../api';
import { useApi } from '@backstage/core-plugin-api';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { notificationsTranslationRef } from '../../translation';
import { UserNotificationSettingsPanel } from './UserNotificationSettingsPanel';
import { capitalize } from 'lodash';
@@ -33,11 +35,9 @@ const NotificationFormatContext = createContext<FormatContextType | undefined>(
);
export const useNotificationFormat = () => {
const { t } = useTranslationRef(notificationsTranslationRef);
const context = useContext(NotificationFormatContext);
if (!context)
throw new Error(
'useNotificationFormat must be used within a NotificationFormatProvider',
);
if (!context) throw new Error(t('settings.errors.useNotificationFormat'));
return context;
};
@@ -83,6 +83,7 @@ export const UserNotificationSettingsCard = (props: {
originNames?: Record<string, string>;
topicNames?: Record<string, string>;
}) => {
const { t } = useTranslationRef(notificationsTranslationRef);
const [settings, setNotificationSettings] = useState<
NotificationSettings | undefined
>(undefined);
@@ -105,9 +106,9 @@ export const UserNotificationSettingsCard = (props: {
};
return (
<InfoCard title="Notification settings" variant="gridItem">
<InfoCard title={t('settings.title')} variant="gridItem">
{loading && <Progress />}
{error && <ErrorPanel title="Failed to load settings" error={error} />}
{error && <ErrorPanel title={t('settings.errorTitle')} error={error} />}
{settings && (
<NotificationFormatProvider
originMap={props.originNames}
@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { render, screen } from '@testing-library/react';
import { screen } from '@testing-library/react';
import { renderInTestApp } from '@backstage/test-utils';
import { UserNotificationSettingsPanel } from './UserNotificationSettingsPanel';
import { NotificationFormatProvider } from './UserNotificationSettingsCard.tsx';
describe('UserNotificationSettingsPanel', () => {
it('renders each origin only once even if present in multiple channels', () => {
it('renders each origin only once even if present in multiple channels', async () => {
const settings = {
channels: [
{
@@ -43,7 +44,7 @@ describe('UserNotificationSettingsPanel', () => {
},
],
};
render(
await renderInTestApp(
<NotificationFormatProvider originMap={{}} topicMap={{}}>
<UserNotificationSettingsPanel
settings={settings}
+120
View File
@@ -0,0 +1,120 @@
/*
* 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 notificationsTranslationRef = createTranslationRef({
id: 'plugin.notifications',
messages: {
notificationsPage: {
title: 'Notifications',
tableTitle: {
all_one: 'All notifications ({{count}})',
all_other: 'All notifications ({{count}})',
saved_one: 'Saved notifications ({{count}})',
saved_other: 'Saved notifications ({{count}})',
unread_one: 'Unread notifications ({{count}})',
unread_other: 'Unread notifications ({{count}})',
read_one: 'Read notifications ({{count}})',
read_other: 'Read notifications ({{count}})',
},
},
filters: {
title: 'Filters',
view: {
label: 'View',
unread: 'Unread notifications',
read: 'Read notifications',
saved: 'Saved',
all: 'All',
},
createdAfter: {
label: 'Sent out',
placeholder: 'Notifications since',
last24h: 'Last 24h',
lastWeek: 'Last week',
anyTime: 'Any time',
},
sortBy: {
label: 'Sort by',
placeholder: 'Field to sort by',
newest: 'Newest on top',
oldest: 'Oldest on top',
topic: 'Topic',
origin: 'Origin',
},
severity: {
label: 'Min severity',
critical: 'Critical',
high: 'High',
normal: 'Normal',
low: 'Low',
},
topic: {
label: 'Topic',
anyTopic: 'Any topic',
},
},
table: {
emptyMessage: 'No records to display',
pagination: {
firstTooltip: 'First Page',
labelDisplayedRows: '{from}-{to} of {count}',
labelRowsSelect: 'rows',
lastTooltip: 'Last Page',
nextTooltip: 'Next Page',
previousTooltip: 'Previous Page',
},
bulkActions: {
markAllRead: 'Mark all read',
markSelectedAsRead: 'Mark selected as read',
returnSelectedAmongUnread: 'Return selected among unread',
saveSelectedForLater: 'Save selected for later',
undoSaveForSelected: 'Undo save for selected',
},
confirmDialog: {
title: 'Are you sure?',
markAllReadDescription: 'Mark <b>all</b> notifications as <b>read</b>.',
markAllReadConfirmation: 'Mark All',
},
errors: {
markAllReadFailed: 'Failed to mark all notifications as read',
},
},
sidebar: {
title: 'Notifications',
errors: {
markAsReadFailed: 'Failed to mark notification as read',
fetchNotificationFailed: 'Failed to fetch notification',
},
},
settings: {
title: 'Notification settings',
errorTitle: 'Failed to load settings',
noSettingsAvailable:
'No notification settings available, check back later',
table: {
origin: 'Origin',
topic: 'Topic',
},
errors: {
useNotificationFormat:
'useNotificationFormat must be used within a NotificationFormatProvider',
},
},
},
});