Groups: Make strings localizable

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2022-12-17 00:33:07 +01:00
parent 0d617d4de8
commit fd6e3f4032
7 changed files with 93 additions and 37 deletions

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { Avatar, HStack, Icon, Stack, Text } from './ui'; import { Avatar, HStack, Icon, Stack, Text } from './ui';
@ -31,23 +31,23 @@ const GroupCard: React.FC<IGroupCard> = ({ group }) => {
{group.relationship?.role === 'admin' ? ( {group.relationship?.role === 'admin' ? (
<HStack space={1} alignItems='center'> <HStack space={1} alignItems='center'>
<Icon className='h-4 w-4' src={require('@tabler/icons/users.svg')} /> <Icon className='h-4 w-4' src={require('@tabler/icons/users.svg')} />
<span>Owner</span> <span><FormattedMessage id='group.role.owner' defaultMessage='Owner' /></span>
</HStack> </HStack>
) : group.relationship?.role === 'moderator' && ( ) : group.relationship?.role === 'moderator' && (
<HStack space={1} alignItems='center'> <HStack space={1} alignItems='center'>
<Icon className='h-4 w-4' src={require('@tabler/icons/gavel.svg')} /> <Icon className='h-4 w-4' src={require('@tabler/icons/gavel.svg')} />
<span>Moderator</span> <span><FormattedMessage id='group.role.moderator' defaultMessage='Moderator' /></span>
</HStack> </HStack>
)} )}
{group.locked ? ( {group.locked ? (
<HStack space={1} alignItems='center'> <HStack space={1} alignItems='center'>
<Icon className='h-4 w-4' src={require('@tabler/icons/lock.svg')} /> <Icon className='h-4 w-4' src={require('@tabler/icons/lock.svg')} />
<span>Private</span> <span><FormattedMessage id='group.privacy.locked' defaultMessage='Private' /></span>
</HStack> </HStack>
) : ( ) : (
<HStack space={1} alignItems='center'> <HStack space={1} alignItems='center'>
<Icon className='h-4 w-4' src={require('@tabler/icons/world.svg')} /> <Icon className='h-4 w-4' src={require('@tabler/icons/world.svg')} />
<span>Public</span> <span><FormattedMessage id='group.privacy.public' defaultMessage='Public' /></span>
</HStack> </HStack>
)} )}
</HStack> </HStack>

View File

@ -13,6 +13,9 @@ import type { Group } from 'soapbox/types/entities';
const messages = defineMessages({ const messages = defineMessages({
header: { id: 'group.header.alt', defaultMessage: 'Group header' }, header: { id: 'group.header.alt', defaultMessage: 'Group header' },
confirmationHeading: { id: 'confirmations.leave_group.heading', defaultMessage: 'Leave group' },
confirmationMessage: { id: 'confirmations.leave_group.message', defaultMessage: 'You are about to leave the group. Do you want to continue?' },
confirmationConfirm: { id: 'confirmations.leave_group.confirm', defaultMessage: 'Leave' },
}); });
interface IGroupHeader { interface IGroupHeader {
@ -49,9 +52,9 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
const onLeaveGroup = () => { const onLeaveGroup = () => {
dispatch(openModal('CONFIRM', { dispatch(openModal('CONFIRM', {
heading: 'Leave group', heading: intl.formatMessage(messages.confirmationHeading),
message: 'You are about to leave the group. Do you want to continue?', message: intl.formatMessage(messages.confirmationMessage),
confirm: 'Leave', confirm: intl.formatMessage(messages.confirmationConfirm),
onConfirm: () => { onConfirm: () => {
dispatch(leaveGroup(group.id)); dispatch(leaveGroup(group.id));
}, },
@ -99,7 +102,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
theme='primary' theme='primary'
onClick={onJoinGroup} onClick={onJoinGroup}
> >
{group.locked ? 'Request to join group' : 'Join group'} {group.locked ? <FormattedMessage id='group.request_join' defaultMessage='Request to join group' /> : <FormattedMessage id='group.join' defaultMessage='Join group' />}
</Button> </Button>
); );
} }
@ -110,7 +113,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
theme='secondary' theme='secondary'
onClick={onEditGroup} onClick={onEditGroup}
> >
<FormattedMessage id='group.manage' defaultMessage='Edit group' /> <FormattedMessage id='group.manage' defaultMessage='Edit group' />
</Button> </Button>
); );
} }
@ -120,7 +123,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
theme='secondary' theme='secondary'
onClick={onLeaveGroup} onClick={onLeaveGroup}
> >
<FormattedMessage id='group.leave' defaultMessage='Leave group' /> <FormattedMessage id='group.leave' defaultMessage='Leave group' />
</Button> </Button>
); );
}; };
@ -153,23 +156,23 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
{group.relationship?.role === 'admin' ? ( {group.relationship?.role === 'admin' ? (
<HStack space={1} alignItems='center'> <HStack space={1} alignItems='center'>
<Icon className='h-4 w-4' src={require('@tabler/icons/users.svg')} /> <Icon className='h-4 w-4' src={require('@tabler/icons/users.svg')} />
<span>Owner</span> <span><FormattedMessage id='group.role.owner' defaultMessage='Owner' /></span>
</HStack> </HStack>
) : group.relationship?.role === 'moderator' && ( ) : group.relationship?.role === 'moderator' && (
<HStack space={1} alignItems='center'> <HStack space={1} alignItems='center'>
<Icon className='h-4 w-4' src={require('@tabler/icons/gavel.svg')} /> <Icon className='h-4 w-4' src={require('@tabler/icons/gavel.svg')} />
<span>Moderator</span> <span><FormattedMessage id='group.role.moderator' defaultMessage='Moderator' /></span>
</HStack> </HStack>
)} )}
{group.locked ? ( {group.locked ? (
<HStack space={1} alignItems='center'> <HStack space={1} alignItems='center'>
<Icon className='h-4 w-4' src={require('@tabler/icons/lock.svg')} /> <Icon className='h-4 w-4' src={require('@tabler/icons/lock.svg')} />
<span>Private</span> <span><FormattedMessage id='group.privacy.locked' defaultMessage='Private' /></span>
</HStack> </HStack>
) : ( ) : (
<HStack space={1} alignItems='center'> <HStack space={1} alignItems='center'>
<Icon className='h-4 w-4' src={require('@tabler/icons/world.svg')} /> <Icon className='h-4 w-4' src={require('@tabler/icons/world.svg')} />
<span>Public</span> <span><FormattedMessage id='group.privacy.public' defaultMessage='Public' /></span>
</HStack> </HStack>
)} )}
</HStack> </HStack>

View File

@ -17,7 +17,6 @@ interface IGroupMembers {
} }
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.group_members', defaultMessage: 'Group members' },
adminSubheading: { id: 'groups.admin_subheading', defaultMessage: 'Group administrators' }, adminSubheading: { id: 'groups.admin_subheading', defaultMessage: 'Group administrators' },
moderatorSubheading: { id: 'groups.moderator_subheading', defaultMessage: 'Group moderators' }, moderatorSubheading: { id: 'groups.moderator_subheading', defaultMessage: 'Group moderators' },
userSubheading: { id: 'groups.user_subheading', defaultMessage: 'Users' }, userSubheading: { id: 'groups.user_subheading', defaultMessage: 'Users' },

View File

@ -30,7 +30,7 @@ const getOrderedGroups = createSelector([
.sort((a, b) => a.display_name.localeCompare(b.display_name)); .sort((a, b) => a.display_name.localeCompare(b.display_name));
}); });
const Lists: React.FC = () => { const Groups: React.FC = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const groups = useAppSelector((state) => getOrderedGroups(state)); const groups = useAppSelector((state) => getOrderedGroups(state));
@ -69,7 +69,7 @@ const Lists: React.FC = () => {
return ( return (
<ScrollableList <ScrollableList
scrollKey='lists' scrollKey='groups'
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
itemClassName='py-3 last:pb-0' itemClassName='py-3 last:pb-0'
> >
@ -82,4 +82,4 @@ const Lists: React.FC = () => {
); );
}; };
export default Lists; export default Groups;

View File

@ -27,25 +27,27 @@ const PrivacyStep = () => {
</Stack> </Stack>
<Form> <Form>
<FormGroup <FormGroup
labelText='Privacy settings' labelText={<FormattedMessage id='manage_group.privacy.label' defaultMessage='Privacy settings' />}
> >
<List> <List>
<ListItem <ListItem
label='Public' label={<FormattedMessage id='manage_group.privacy.public.label' defaultMessage='Public' />}
hint='Discoverable. Anyone can join.' hint={<FormattedMessage id='manage_group.privacy.public.hint' defaultMessage='Discoverable. Anyone can join.' />}
onSelect={() => onChangePrivacy(false)} onSelect={() => onChangePrivacy(false)}
isSelected={!locked} isSelected={!locked}
/> />
<ListItem <ListItem
label='Private (Owner approval required)' label={<FormattedMessage id='manage_group.privacy.private.label' defaultMessage='Private (Owner approval required)' />}
hint='Discoverable. Users can join after their request is approved.' hint={<FormattedMessage id='manage_group.privacy.private.hint' defaultMessage='Discoverable. Users can join after their request is approved.' />}
onSelect={() => onChangePrivacy(true)} onSelect={() => onChangePrivacy(true)}
isSelected={locked} isSelected={locked}
/> />
</List> </List>
</FormGroup> </FormGroup>
<Text size='sm' theme='muted' align='center'>These settings cannot be changed later.</Text> <Text size='sm' theme='muted' align='center'>
<FormattedMessage id='manage_group.privacy.hint' defaultMessage='These settings cannot be changed later.' />
</Text>
</Form> </Form>
</> </>
); );

View File

@ -457,6 +457,9 @@
"confirmations.domain_block.message": "Czy na pewno chcesz zablokować całą domenę {domain}? Zwykle lepszym rozwiązaniem jest blokada lub wyciszenie kilku użytkowników.", "confirmations.domain_block.message": "Czy na pewno chcesz zablokować całą domenę {domain}? Zwykle lepszym rozwiązaniem jest blokada lub wyciszenie kilku użytkowników.",
"confirmations.leave_event.confirm": "Opuść wydarzenie", "confirmations.leave_event.confirm": "Opuść wydarzenie",
"confirmations.leave_event.message": "Jeśli będziesz chciał(a) dołączyć do wydarzenia jeszcze raz, prośba będzie musiała zostać ponownie zatwierdzona. Czy chcesz kontynuować?", "confirmations.leave_event.message": "Jeśli będziesz chciał(a) dołączyć do wydarzenia jeszcze raz, prośba będzie musiała zostać ponownie zatwierdzona. Czy chcesz kontynuować?",
"confirmations.leave_group.confirm": "Opuść",
"confirmations.leave_group.message": "Czy na pewno chcesz opuścić tę grupę?",
"confirmations.leave_group.heading": "Opuść grupę",
"confirmations.mute.confirm": "Wycisz", "confirmations.mute.confirm": "Wycisz",
"confirmations.mute.heading": "Wycisz @{name}", "confirmations.mute.heading": "Wycisz @{name}",
"confirmations.mute.message": "Czy na pewno chcesz wyciszyć {name}?", "confirmations.mute.message": "Czy na pewno chcesz wyciszyć {name}?",
@ -611,6 +614,7 @@
"empty_column.filters": "Nie wyciszyłeś(-aś) jeszcze żadnego słowa.", "empty_column.filters": "Nie wyciszyłeś(-aś) jeszcze żadnego słowa.",
"empty_column.follow_recommendations": "Wygląda na to, że nie można wygenerować dla Ciebie sugestii kont do obserwacji. Możesz spróbować użyć wyszukiwania aby odnaleźć ciekawe profile, lub przejrzeć trendujące hashtagi.", "empty_column.follow_recommendations": "Wygląda na to, że nie można wygenerować dla Ciebie sugestii kont do obserwacji. Możesz spróbować użyć wyszukiwania aby odnaleźć ciekawe profile, lub przejrzeć trendujące hashtagi.",
"empty_column.follow_requests": "Nie masz żadnych próśb o możliwość obserwacji. Kiedy ktoś utworzy ją, pojawi się tutaj.", "empty_column.follow_requests": "Nie masz żadnych próśb o możliwość obserwacji. Kiedy ktoś utworzy ją, pojawi się tutaj.",
"empty_column.group": "Nie ma wpisów w tej grupie.",
"empty_column.hashtag": "Nie ma wpisów oznaczonych tym hashtagiem. Możesz napisać pierwszy(-a)!", "empty_column.hashtag": "Nie ma wpisów oznaczonych tym hashtagiem. Możesz napisać pierwszy(-a)!",
"empty_column.home": "Możesz też odwiedzić {public}, aby znaleźć innych użytkowników.", "empty_column.home": "Możesz też odwiedzić {public}, aby znaleźć innych użytkowników.",
"empty_column.home.local_tab": "zakładkę {site_title}", "empty_column.home.local_tab": "zakładkę {site_title}",
@ -626,6 +630,7 @@
"empty_column.remote": "Tu nic nie ma! Zaobserwuj użytkowników {instance}, aby wypełnić tę oś.", "empty_column.remote": "Tu nic nie ma! Zaobserwuj użytkowników {instance}, aby wypełnić tę oś.",
"empty_column.scheduled_statuses": "Nie masz żadnych zaplanowanych wpisów. Kiedy dodasz jakiś, pojawi się on tutaj.", "empty_column.scheduled_statuses": "Nie masz żadnych zaplanowanych wpisów. Kiedy dodasz jakiś, pojawi się on tutaj.",
"empty_column.search.accounts": "Brak wyników wyszukiwania osób dla „{term}”", "empty_column.search.accounts": "Brak wyników wyszukiwania osób dla „{term}”",
"empty_column.search.groups": "Brak wyników wyszukiwania grup dla „{term}”",
"empty_column.search.hashtags": "Brak wyników wyszukiwania hashtagów dla „{term}”", "empty_column.search.hashtags": "Brak wyników wyszukiwania hashtagów dla „{term}”",
"empty_column.search.statuses": "Brak wyników wyszukiwania wpisów dla „{term}”", "empty_column.search.statuses": "Brak wyników wyszukiwania wpisów dla „{term}”",
"empty_column.test": "Testowa oś czasu jest pusta.", "empty_column.test": "Testowa oś czasu jest pusta.",
@ -695,6 +700,22 @@
"gdpr.message": "{siteTitle} korzysta z ciasteczek sesji, które są niezbędne dla działania strony.", "gdpr.message": "{siteTitle} korzysta z ciasteczek sesji, które są niezbędne dla działania strony.",
"gdpr.title": "{siteTitle} korzysta z ciasteczek", "gdpr.title": "{siteTitle} korzysta z ciasteczek",
"getting_started.open_source_notice": "{code_name} jest oprogramowaniem o otwartym źródle. Możesz pomóc w rozwoju lub zgłaszać błędy na GitLabie tutaj: {code_link} (v{code_version}).", "getting_started.open_source_notice": "{code_name} jest oprogramowaniem o otwartym źródle. Możesz pomóc w rozwoju lub zgłaszać błędy na GitLabie tutaj: {code_link} (v{code_version}).",
"groups.admin_subheading": "Administratorzy grupy",
"groups.empty.title": "Brak grup",
"groups.empty.subtitle": "Odkrywaj grupy do których możesz dołączyć lub utwórz własną.",
"groups.moderator_subheading": "Moderatorzy grupy",
"groups.user_subheading": "Członkowie grupy",
"group.header.alt": "Nagłówek grupy",
"group.join": "Dołącz do grupy",
"group.leave": "Opuść grupę",
"group.manage": "Edytuj grupę",
"group.request_join": "Poproś o dołączenie do grupy",
"group.role.owner": "Właściciel",
"group.role.moderator": "Moderator",
"group.privacy.locked": "Prywatna",
"group.privacy.public": "Publiczna",
"group.tabs.all": "Wszystko",
"group.tabs.members": "Członkowie",
"hashtag.column_header.tag_mode.all": "i {additional}", "hashtag.column_header.tag_mode.all": "i {additional}",
"hashtag.column_header.tag_mode.any": "lub {additional}", "hashtag.column_header.tag_mode.any": "lub {additional}",
"hashtag.column_header.tag_mode.none": "bez {additional}", "hashtag.column_header.tag_mode.none": "bez {additional}",
@ -798,6 +819,21 @@
"login_external.errors.instance_fail": "Instancja zwróciła błąd.", "login_external.errors.instance_fail": "Instancja zwróciła błąd.",
"login_external.errors.network_fail": "Połączenie nie powiodło się. Czy jest blokowane przez wtyczkę do przeglądarki?", "login_external.errors.network_fail": "Połączenie nie powiodło się. Czy jest blokowane przez wtyczkę do przeglądarki?",
"login_form.header": "Zaloguj się", "login_form.header": "Zaloguj się",
"manage_group.create": "Utwórz",
"manage_group.fields.name_label": "Nazwa grupy (wymagana)",
"manage_group.fields.name_placeholder": "Nazwa grupy",
"manage_group.fields.description_label": "Opis",
"manage_group.fields.description_placeholder": "Opis",
"manage_group.get_started": "Rozpocznijmy!",
"manage_group.next": "Dalej",
"manage_group.privacy.hint": "To ustawienie nie może zostać później zmienione.",
"manage_group.privacy.label": "Ustawienia prywatności",
"manage_group.privacy.public.hint": "Widoczna w mechanizmach odkrywania. Każdy może dołączyć.",
"manage_group.privacy.public.label": "Publiczna",
"manage_group.privacy.private.hint": "Widoczna w mechanizmach odkrywania. Użytkownicy mogą dołączyć po zatwierdzeniu ich prośby.",
"manage_group.privacy.private.label": "Prywatna (wymaga zatwierdzenia przez właściciela)",
"manage_group.tagline": "Grupy pozwalają łączyć ludzi o podobnych zainteresowaniach.",
"manage_group.update": "Aktualizuj",
"media_panel.empty_message": "Nie znaleziono mediów.", "media_panel.empty_message": "Nie znaleziono mediów.",
"media_panel.title": "Media", "media_panel.title": "Media",
"mfa.confirm.success_message": "Potwierdzono MFA", "mfa.confirm.success_message": "Potwierdzono MFA",
@ -864,6 +900,7 @@
"navigation_bar.compose_quote": "Cytuj wpis", "navigation_bar.compose_quote": "Cytuj wpis",
"navigation_bar.compose_reply": "Odpowiedz na wpis", "navigation_bar.compose_reply": "Odpowiedz na wpis",
"navigation_bar.create_event": "Utwórz nowe wydarzenie", "navigation_bar.create_event": "Utwórz nowe wydarzenie",
"navigation_bar.create_group": "Utwórz grupę",
"navigation_bar.domain_blocks": "Ukryte domeny", "navigation_bar.domain_blocks": "Ukryte domeny",
"navigation_bar.favourites": "Ulubione", "navigation_bar.favourites": "Ulubione",
"navigation_bar.filters": "Wyciszone słowa", "navigation_bar.filters": "Wyciszone słowa",
@ -872,10 +909,14 @@
"navigation_bar.in_reply_to": "W odpowiedzi do", "navigation_bar.in_reply_to": "W odpowiedzi do",
"navigation_bar.invites": "Zaproszenia", "navigation_bar.invites": "Zaproszenia",
"navigation_bar.logout": "Wyloguj", "navigation_bar.logout": "Wyloguj",
"navigation_bar.manage_group": "Zarządzaj grupą",
"navigation_bar.mutes": "Wyciszeni użytkownicy", "navigation_bar.mutes": "Wyciszeni użytkownicy",
"navigation_bar.preferences": "Preferencje", "navigation_bar.preferences": "Preferencje",
"navigation_bar.profile_directory": "Katalog profilów", "navigation_bar.profile_directory": "Katalog profilów",
"navigation_bar.soapbox_config": "Konfiguracja Soapbox", "navigation_bar.soapbox_config": "Konfiguracja Soapbox",
"new_group_panel.action": "Utwórz grupę",
"new_group_panel.subtitle": "Nie możesz znaleźć tego, czego szukasz? Utwórz własną prywatną lub publiczną grupę.",
"new_group_panel.title": "Utwórz nową grupę",
"notification.favourite": "{name} dodał(a) Twój wpis do ulubionych", "notification.favourite": "{name} dodał(a) Twój wpis do ulubionych",
"notification.follow": "{name} zaczął(-ęła) Cię obserwować", "notification.follow": "{name} zaczął(-ęła) Cię obserwować",
"notification.follow_request": "{name} poprosił(a) Cię o możliwość obserwacji", "notification.follow_request": "{name} poprosił(a) Cię o możliwość obserwacji",
@ -1103,6 +1144,7 @@
"search.placeholder": "Szukaj", "search.placeholder": "Szukaj",
"search_results.accounts": "Ludzie", "search_results.accounts": "Ludzie",
"search_results.filter_message": "Szukasz wpisów autorstwa @{acct}.", "search_results.filter_message": "Szukasz wpisów autorstwa @{acct}.",
"search_results.groups": "Grupy",
"search_results.hashtags": "Hashtagi", "search_results.hashtags": "Hashtagi",
"search_results.statuses": "Wpisy", "search_results.statuses": "Wpisy",
"security.codes.fail": "Nie udało się uzyskać zapasowych kodów", "security.codes.fail": "Nie udało się uzyskać zapasowych kodów",
@ -1291,6 +1333,7 @@
"tabs_bar.chats": "Rozmowy", "tabs_bar.chats": "Rozmowy",
"tabs_bar.dashboard": "Panel administracyjny", "tabs_bar.dashboard": "Panel administracyjny",
"tabs_bar.fediverse": "Fediwersum", "tabs_bar.fediverse": "Fediwersum",
"tabs_bar.groups": "Grupy",
"tabs_bar.home": "Strona główna", "tabs_bar.home": "Strona główna",
"tabs_bar.local": "Lokalna", "tabs_bar.local": "Lokalna",
"tabs_bar.more": "Więcej", "tabs_bar.more": "Więcej",

View File

@ -1,4 +1,5 @@
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useRouteMatch } from 'react-router-dom'; import { useRouteMatch } from 'react-router-dom';
import { fetchGroup } from 'soapbox/actions/groups'; import { fetchGroup } from 'soapbox/actions/groups';
@ -16,6 +17,11 @@ import { makeGetGroup } from 'soapbox/selectors';
import { Tabs } from '../components/ui'; import { Tabs } from '../components/ui';
const messages = defineMessages({
all: { id: 'group.tabs.all', defaultMessage: 'All' },
members: { id: 'group.tabs.members', defaultMessage: 'Members' },
});
interface IGroupPage { interface IGroupPage {
params?: { params?: {
id?: string, id?: string,
@ -24,6 +30,7 @@ interface IGroupPage {
/** Page to display a group. */ /** Page to display a group. */
const GroupPage: React.FC<IGroupPage> = ({ params, children }) => { const GroupPage: React.FC<IGroupPage> = ({ params, children }) => {
const intl = useIntl();
const match = useRouteMatch(); const match = useRouteMatch();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -43,6 +50,19 @@ const GroupPage: React.FC<IGroupPage> = ({ params, children }) => {
); );
} }
const items = [
{
text: intl.formatMessage(messages.all),
to: `/groups/${group?.id}`,
name: '/groups/:id',
},
{
text: intl.formatMessage(messages.members),
to: `/groups/${group?.id}/members`,
name: '/groups/:id/members',
},
]
return ( return (
<> <>
<Layout.Main> <Layout.Main>
@ -50,18 +70,7 @@ const GroupPage: React.FC<IGroupPage> = ({ params, children }) => {
<GroupHeader group={group} /> <GroupHeader group={group} />
<Tabs <Tabs
items={[ items={items}
{
text: 'All',
to: `/groups/${group?.id}`,
name: '/groups/:id',
},
{
text: 'Members',
to: `/groups/${group?.id}/members`,
name: '/groups/:id/members',
},
]}
activeItem={match.path} activeItem={match.path}
/> />