feat: new context bar for moderation console (#29798)

* add accordion to view reported messages

* add changeset

* update i18n and add new report message action

* update the changeset

* useToggle, fix format

* fix ts and lint

---------

Co-authored-by: Tasso Evangelista <tasso.evangelista@rocket.chat>
pull/30026/head
デヴぁんす 2 years ago committed by GitHub
parent 05f7536cc0
commit 074db3b419
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      .changeset/slimy-wasps-double.md
  2. 16
      apps/meteor/client/views/admin/moderation/MessageContextFooter.tsx
  3. 55
      apps/meteor/client/views/admin/moderation/MessageReportInfo.tsx
  4. 8
      apps/meteor/client/views/admin/moderation/ModerationConsolePage.tsx
  5. 48
      apps/meteor/client/views/admin/moderation/UserMessages.tsx
  6. 15
      apps/meteor/client/views/admin/moderation/helpers/ContextMessage.tsx
  7. 26
      apps/meteor/client/views/admin/moderation/helpers/ReportReason.tsx
  8. 25
      apps/meteor/client/views/admin/moderation/helpers/ReportReasonCollapsible.tsx
  9. 4
      apps/meteor/client/views/admin/moderation/hooks/useDeactivateUserAction.tsx
  10. 3
      apps/meteor/client/views/admin/moderation/hooks/useDeleteMessage.tsx
  11. 2
      apps/meteor/client/views/admin/moderation/hooks/useDeleteMessagesAction.tsx
  12. 48
      apps/meteor/client/views/admin/moderation/hooks/useDismissMessageAction.tsx
  13. 14
      apps/meteor/client/views/admin/moderation/hooks/useDismissUserAction.tsx
  14. 2
      apps/meteor/client/views/admin/moderation/hooks/useResetAvatarAction.tsx
  15. 53
      apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json

@ -0,0 +1,7 @@
---
'@rocket.chat/meteor': minor
'@rocket.chat/ui-contexts': minor
---
UX improvement for the Moderation Console Context bar for viewing the reported messages. The Report reason is now displayed in the reported messages context bar.
The Moderation Action Modal confirmation description is updated to be more clear and concise.

@ -10,24 +10,20 @@ import useResetAvatarAction from './hooks/useResetAvatarAction';
const MessageContextFooter: FC<{ userId: string; deleted: boolean }> = ({ userId, deleted }) => {
const t = useTranslation();
const { action: dismissReportAction } = useDismissUserAction(userId);
const { action } = useDeleteMessagesAction(userId);
return (
<ButtonGroup flexGrow={1}>
<Button
icon='trash'
flexGrow={1}
onClick={action}
title={t('delete-message')}
aria-label={t('Moderation_Delete_all_messages')}
danger
>
<ButtonGroup width='full' stretch>
<Button onClick={dismissReportAction} title={t('Moderation_Dismiss_all_reports')} aria-label={t('Moderation_Dismiss_reports')}>
{t('Moderation_Dismiss_all_reports')}
</Button>
<Button onClick={action} title={t('delete-message')} aria-label={t('Moderation_Delete_all_messages')} secondary danger>
{t('Moderation_Delete_all_messages')}
</Button>
<Menu
options={{
approve: useDismissUserAction(userId),
deactivate: { ...useDeactivateUserAction(userId), ...(deleted && { disabled: true }) },
resetAvatar: { ...useResetAvatarAction(userId), ...(deleted && { disabled: true }) },
}}

@ -1,25 +1,15 @@
import { Box, Message, MessageName, MessageUsername } from '@rocket.chat/fuselage';
import { useEndpoint, useRoute, useSetting, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import { Box, Message } from '@rocket.chat/fuselage';
import { useEndpoint, useSetting, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import { getUserDisplayName } from '../../../../lib/getUserDisplayName';
import { ContextualbarHeader, ContextualbarBack, ContextualbarTitle, ContextualbarClose } from '../../../components/Contextualbar';
import UserAvatar from '../../../components/avatar/UserAvatar';
import { useFormatDate } from '../../../hooks/useFormatDate';
import { useFormatDateAndTime } from '../../../hooks/useFormatDateAndTime';
import { useFormatTime } from '../../../hooks/useFormatTime';
import { router } from '../../../providers/RouterProvider';
import ReportReason from './helpers/ReportReason';
const MessageReportInfo = ({ msgId }: { msgId: string }): JSX.Element => {
const t = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();
const getReportsByMessage = useEndpoint('GET', `/v1/moderation.reports`);
const moderationRoute = useRoute('moderation-console');
const formatDateAndTime = useFormatDateAndTime();
const formatTime = useFormatTime();
const formatDate = useFormatDate();
const useRealName = Boolean(useSetting('UI_Use_Real_Name'));
const {
@ -60,39 +50,16 @@ const MessageReportInfo = ({ msgId }: { msgId: string }): JSX.Element => {
return (
<>
<ContextualbarHeader>
<ContextualbarBack onClick={() => router.navigate(-1)} />
<ContextualbarTitle>{t('Report')}</ContextualbarTitle>
<ContextualbarClose onClick={() => moderationRoute.push({})} />
</ContextualbarHeader>
{isSuccessReportsByMessage && reportsByMessage?.reports && (
<Box display='flex' flexDirection='column' width='full' height='full' overflowX='hidden' overflowY='auto'>
{reports.map((report) => (
<Box key={report._id}>
<Message.Divider>{formatDate(report.ts)}</Message.Divider>
<Message>
<Message.LeftContainer>
<UserAvatar username={report?.reportedBy?.username || 'rocket.cat'} />
</Message.LeftContainer>
<Message.Container>
<Message.Header>
<MessageName>
{report.reportedBy
? getUserDisplayName(report.reportedBy.name, report.reportedBy.username, useRealName)
: 'Rocket.Cat'}
</MessageName>
<>
{useRealName && (
<MessageUsername>&nbsp;@{report.reportedBy ? report.reportedBy.username : 'rocket.cat'}</MessageUsername>
)}
</>
<Message.Timestamp title={formatDateAndTime(report.ts)}>{formatTime(report.ts)}</Message.Timestamp>
</Message.Header>
<Message.Body>{report.description}</Message.Body>
</Message.Container>
</Message>
</Box>
{reports.map((report, index) => (
<ReportReason
key={report._id}
ind={index + 1}
uinfo={useRealName ? report.reportedBy?.name : report.reportedBy?.username}
msg={report.description}
ts={new Date(report.ts)}
/>
))}
</Box>
)}

@ -4,7 +4,6 @@ import React from 'react';
import { Contextualbar } from '../../../components/Contextualbar';
import Page from '../../../components/Page';
import { getPermaLink } from '../../../lib/getPermaLink';
import MessageReportInfo from './MessageReportInfo';
import ModerationConsoleTable from './ModerationConsoleTable';
import UserMessages from './UserMessages';
@ -32,12 +31,7 @@ const ModerationConsolePage = () => {
<ModerationConsoleTable />
</Page.Content>
</Page>
{context && (
<Contextualbar>
{context === 'info' && id && <UserMessages userId={id} onRedirect={handleRedirect} />}
{context === 'reports' && id && <MessageReportInfo msgId={id} />}
</Contextualbar>
)}
{context && <Contextualbar>{context === 'info' && id && <UserMessages userId={id} onRedirect={handleRedirect} />}</Contextualbar>}
</Page>
);
};

@ -1,12 +1,11 @@
import { Box, Callout, Message } from '@rocket.chat/fuselage';
import { Box, Callout, Message, StatesAction, StatesActions, StatesIcon, StatesTitle } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useEndpoint, useRoute, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import { useEndpoint, useRouter, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import React, { useMemo } from 'react';
import React from 'react';
import { ContextualbarHeader, ContextualbarTitle, ContextualbarClose, ContextualbarFooter } from '../../../components/Contextualbar';
import GenericNoResults from '../../../components/GenericNoResults';
import { useUserDisplayName } from '../../../hooks/useUserDisplayName';
import MessageContextFooter from './MessageContextFooter';
import ContextMessage from './helpers/ContextMessage';
@ -14,7 +13,7 @@ import ContextMessage from './helpers/ContextMessage';
const UserMessages = ({ userId, onRedirect }: { userId: string; onRedirect: (mid: string) => void }): JSX.Element => {
const t = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();
const moderationRoute = useRoute('moderation-console');
const moderationRoute = useRouter();
const getUserMessages = useEndpoint('GET', '/v1/moderation.user.reportedMessages');
const {
@ -22,6 +21,7 @@ const UserMessages = ({ userId, onRedirect }: { userId: string; onRedirect: (mid
refetch: reloadUserMessages,
isLoading: isLoadingUserMessages,
isSuccess: isSuccessUserMessages,
isError,
} = useQuery(
['moderation.userMessages', { userId }],
async () => {
@ -35,39 +35,15 @@ const UserMessages = ({ userId, onRedirect }: { userId: string; onRedirect: (mid
},
);
// opens up the 'reports' tab when the user clicks on a user in the 'users' tab
const handleClick = useMutableCallback((id): void => {
moderationRoute.push({
context: 'reports',
id,
});
});
const handleChange = useMutableCallback(() => {
reloadUserMessages();
});
const { username, name } = useMemo(() => {
return (
report?.user ??
report?.messages?.[0]?.message?.u ?? {
username: t('Deleted_user'),
name: t('Deleted_user'),
}
);
}, [report?.messages, report?.user, t]);
const displayName =
useUserDisplayName({
name,
username,
}) || userId;
return (
<>
<ContextualbarHeader>
<ContextualbarTitle>{t('Moderation_Message_context_header', { displayName })}</ContextualbarTitle>
<ContextualbarClose onClick={() => moderationRoute.push({})} />
<ContextualbarTitle>{t('Moderation_Message_context_header')}</ContextualbarTitle>
<ContextualbarClose onClick={() => moderationRoute.navigate('/admin/moderation', { replace: true })} />
</ContextualbarHeader>
<Box display='flex' flexDirection='column' width='full' height='full' overflowY='auto' overflowX='hidden'>
{isLoadingUserMessages && <Message>{t('Loading')}</Message>}
@ -95,7 +71,6 @@ const UserMessages = ({ userId, onRedirect }: { userId: string; onRedirect: (mid
<ContextMessage
message={message.message}
room={message.room}
handleClick={handleClick}
onRedirect={onRedirect}
onChange={handleChange}
deleted={!report.user}
@ -103,6 +78,15 @@ const UserMessages = ({ userId, onRedirect }: { userId: string; onRedirect: (mid
</Box>
))}
{isSuccessUserMessages && report.messages.length === 0 && <GenericNoResults />}
{isError && (
<Box display='flex' flexDirection='column' alignItems='center' pb='x20' color='default'>
<StatesIcon name='warning' variation='danger' />
<StatesTitle>{t('Something_went_wrong')}</StatesTitle>
<StatesActions>
<StatesAction onClick={handleChange}>{t('Reload_page')}</StatesAction>
</StatesActions>
</Box>
)}
</Box>
<ContextualbarFooter display='flex'>
{isSuccessUserMessages && report.messages.length > 0 && <MessageContextFooter userId={userId} deleted={!report.user} />}

@ -12,20 +12,21 @@ import { useFormatDate } from '../../../../hooks/useFormatDate';
import { useFormatDateAndTime } from '../../../../hooks/useFormatDateAndTime';
import { useFormatTime } from '../../../../hooks/useFormatTime';
import { useUserDisplayName } from '../../../../hooks/useUserDisplayName';
import MessageReportInfo from '../MessageReportInfo';
import useDeleteMessage from '../hooks/useDeleteMessage';
import { useDismissMessageAction } from '../hooks/useDismissMessageAction';
import ReportReasonCollapsible from './ReportReasonCollapsible';
const ContextMessage = ({
message,
room,
deleted,
handleClick,
onRedirect,
onChange,
}: {
message: any;
room: IModerationReport['room'];
deleted: boolean;
handleClick: (id: IMessage['_id']) => void;
onRedirect: (id: IMessage['_id']) => void;
onChange: () => void;
}): JSX.Element => {
@ -34,6 +35,7 @@ const ContextMessage = ({
const isEncryptedMessage = isE2EEMessage(message);
const deleteMessage = useDeleteMessage(message._id, message.rid, onChange);
const dismissMsgReport = useDismissMessageAction(message._id);
const formatDateAndTime = useFormatDateAndTime();
const formatTime = useFormatTime();
@ -76,10 +78,17 @@ const ContextMessage = ({
{message.blocks && <UiKitMessageBlock mid={message._id} blocks={message.blocks} appId rid={message.rid} />}
{message.attachments && <Attachments attachments={message.attachments} />}
</Message.Body>
<ReportReasonCollapsible>
<MessageReportInfo msgId={message._id} />
</ReportReasonCollapsible>
</Message.Container>
<MessageToolboxWrapper>
<Message.Toolbox>
<MessageToolboxItem icon='document-eye' title={t('Moderation_View_reports')} onClick={() => handleClick(message._id)} />
<MessageToolboxItem
icon='checkmark-circled'
title={t('Moderation_Dismiss_reports')}
onClick={() => dismissMsgReport.action()}
/>
<MessageToolboxItem icon='arrow-forward' title={t('Moderation_Go_to_message')} onClick={() => onRedirect(message._id)} />
<MessageToolboxItem disabled={deleted} icon='trash' title={t('Moderation_Delete_message')} onClick={() => deleteMessage()} />
</Message.Toolbox>

@ -0,0 +1,26 @@
import { Box, Tag } from '@rocket.chat/fuselage';
import React from 'react';
import { useFormatDate } from '../../../../hooks/useFormatDate';
const ReportReason = ({ ind, uinfo, msg, ts }: { ind: number; uinfo: string | undefined; msg: string; ts: Date }): JSX.Element => {
const formatDate = useFormatDate();
return (
<Box display='flex' flexDirection='column' alignItems='flex-start' marginBlock={10}>
<Tag variant='danger'>Report #{ind}</Tag>
<Box marginBlock={5} fontSize='p2b'>
{msg}
</Box>
<Box>
<Box is='span' fontWeight='700' color='font-info' fontSize='micro'>
@{uinfo || 'rocket.cat'}
</Box>{' '}
<Box is='span' fontWeight='700' color='font-annotation' fontSize='micro'>
{formatDate(ts)}
</Box>
</Box>
</Box>
);
};
export default ReportReason;

@ -0,0 +1,25 @@
import { Box, Button } from '@rocket.chat/fuselage';
import { useToggle } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React from 'react';
import type { ReactNode } from 'react';
const ReportReasonCollapsible = ({ children }: { children: ReactNode }) => {
const [isOpen, setIsOpen] = useToggle(false);
const t = useTranslation();
const toggle = () => setIsOpen((prev) => !prev);
return (
<>
<Box display='flex' flexDirection='row' justifyContent='space-between' alignItems='center'>
<Button small onClick={toggle} aria-expanded={isOpen} aria-controls='report-reasons'>
{isOpen ? t('Moderation_Hide_reports') : t('Moderation_Show_reports')}
</Button>
</Box>
{isOpen && children}
</>
);
};
export default ReportReasonCollapsible;

@ -20,7 +20,7 @@ const useDeactivateUserAction = (userId: string) => {
dispatchToastMessage({ type: 'error', message: error });
},
onSuccess: () => {
dispatchToastMessage({ type: 'success', message: t('User_has_been_deactivated') });
dispatchToastMessage({ type: 'success', message: t('Moderation_User_deactivated') });
},
});
@ -30,7 +30,7 @@ const useDeactivateUserAction = (userId: string) => {
dispatchToastMessage({ type: 'error', message: error });
},
onSuccess: () => {
dispatchToastMessage({ type: 'success', message: t('Deleted') });
dispatchToastMessage({ type: 'success', message: t('Moderation_Messages_deleted') });
},
});

@ -19,7 +19,6 @@ const useDeleteMessage = (mid: string, rid: string, onChange: () => void) => {
setModal();
},
onSuccess: async () => {
dispatchToastMessage({ type: 'success', message: t('Deleted') });
await handleDismissMessage.mutateAsync({ msgId: mid });
},
});
@ -30,7 +29,7 @@ const useDeleteMessage = (mid: string, rid: string, onChange: () => void) => {
dispatchToastMessage({ type: 'error', message: error });
},
onSuccess: () => {
dispatchToastMessage({ type: 'success', message: t('Moderation_Reports_dismissed') });
dispatchToastMessage({ type: 'success', message: t('Moderation_Message_deleted') });
},
onSettled: () => {
onChange();

@ -18,7 +18,7 @@ const useDeleteMessagesAction = (userId: string) => {
dispatchToastMessage({ type: 'error', message: error });
},
onSuccess: () => {
dispatchToastMessage({ type: 'success', message: t('Deleted') });
dispatchToastMessage({ type: 'success', message: t('Moderation_Messages_deleted') });
},
});

@ -0,0 +1,48 @@
import { useEndpoint, useSetModal, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import React from 'react';
import GenericModal from '../../../../components/GenericModal';
export const useDismissMessageAction = (msgId: string): { action: () => void } => {
const t = useTranslation();
const setModal = useSetModal();
const dispatchToastMessage = useToastMessageDispatch();
const queryClient = useQueryClient();
const dismissMessage = useEndpoint('POST', '/v1/moderation.dismissReports');
const handleDismissMessage = useMutation({
mutationFn: dismissMessage,
onError: (error) => {
dispatchToastMessage({ type: 'error', message: error });
},
onSuccess: () => {
dispatchToastMessage({ type: 'success', message: t('Moderation_Reports_dismissed') });
},
});
const onDismissMessage = async () => {
await handleDismissMessage.mutateAsync({ msgId });
queryClient.invalidateQueries({ queryKey: ['moderation.userMessages'] });
setModal();
};
const confirmDismissMessage = (): void => {
setModal(
<GenericModal
title={t('Moderation_Dismiss_reports')}
confirmText={t('Moderation_Dismiss_reports')}
variant='danger'
onConfirm={() => onDismissMessage()}
onCancel={() => setModal()}
>
{t('Moderation_Dismiss_reports_confirm')}
</GenericModal>,
);
};
return {
action: () => confirmDismissMessage(),
};
};

@ -1,4 +1,4 @@
import { useEndpoint, useRoute, useSetModal, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import { useEndpoint, useRouter, useSetModal, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import React from 'react';
@ -8,7 +8,7 @@ const useDismissUserAction = (userId: string) => {
const t = useTranslation();
const setModal = useSetModal();
const dispatchToastMessage = useToastMessageDispatch();
const moderationRoute = useRoute('moderation-console');
const moderationRoute = useRouter();
const queryClient = useQueryClient();
const dismissUser = useEndpoint('POST', '/v1/moderation.dismissReports');
@ -19,7 +19,7 @@ const useDismissUserAction = (userId: string) => {
dispatchToastMessage({ type: 'error', message: error });
},
onSuccess: () => {
dispatchToastMessage({ type: 'success', message: t('Moderation_Reports_dismissed') });
dispatchToastMessage({ type: 'success', message: t('Moderation_Reports_dismissed_plural') });
},
});
@ -27,19 +27,19 @@ const useDismissUserAction = (userId: string) => {
await handleDismissUser.mutateAsync({ userId });
queryClient.invalidateQueries({ queryKey: ['moderation.reports'] });
setModal();
moderationRoute.push({});
moderationRoute.navigate('/admin/moderation', { replace: true });
};
const confirmDismissUser = (): void => {
setModal(
<GenericModal
title={t('Moderation_Dismiss_and_delete')}
confirmText={t('Moderation_Dismiss_and_delete')}
title={t('Moderation_Dismiss_all_reports')}
confirmText={t('Moderation_Dismiss_all_reports')}
variant='danger'
onConfirm={() => onDismissUser()}
onCancel={() => setModal()}
>
{t('Moderation_Are_you_sure_dismiss_and_delete_reports')}
{t('Moderation_Dismiss_all_reports_confirm')}
</GenericModal>,
);
};

@ -18,7 +18,7 @@ const useResetAvatarAction = (userId: string) => {
dispatchToastMessage({ type: 'error', message: error });
},
onSuccess: () => {
dispatchToastMessage({ type: 'success', message: t('Moderation_Avatar_reset_successfully') });
dispatchToastMessage({ type: 'success', message: t('Moderation_Avatar_reset_success') });
},
});

@ -642,17 +642,12 @@
"is_uploading": "is uploading",
"is_recording": "is recording",
"Are_you_sure": "Are you sure?",
"Moderation_Are_you_sure_dismiss_and_delete_reports": "Are you sure you want to dismiss and delete all reports for this user's messages? This action cannot be undone.",
"Are_you_sure_delete_department": "Are you sure you want to delete this department? This action cannot be undone. Please enter the department name to confirm.",
"Moderation_Are_you_sure_you_want_to_deactivate_this_user": "Are you sure you want to deactivate this user and delete all the reported messages? All the messages will be deleted permanently, and user will not be able to log in. This action cannot be undone.",
"Are_you_sure_you_want_to_clear_all_unread_messages": "Are you sure you want to clear all unread messages?",
"Moderation_Are_you_sure_you_want_to_delete_this_message": "Are you sure you want to delete this message and dismiss all reports against this message? The message will be deleted from the message history and no one will be able to see it. This action cannot be undone.",
"Are_you_sure_you_want_to_close_this_chat": "Are you sure you want to close this chat?",
"Are_you_sure_you_want_to_delete_this_record": "Are you sure you want to delete this record?",
"Are_you_sure_you_want_to_delete_your_account": "Are you sure you want to delete your account?",
"Moderation_Are_you_sure_you_want_to_delete_all_reported_messages_from_this_user": "Are you sure you want to delete all reported messages from this user? The messages will be deleted from the message history and no one will be able to see it. This action cannot be undone.",
"Are_you_sure_you_want_to_disable_Facebook_integration": "Are you sure you want to disable Facebook integration?",
"Moderation_Are_you_sure_you_want_to_reset_the_avatar": "Are you sure you want to reset this users avatar? This action cannot be undone.",
"Are_you_sure_you_want_to_reset_the_name_of_all_priorities": "Are you sure you want to reset the name of all priorities?",
"Assets": "Assets",
"Assets_Description": "Modify your workspace's logo, icon, favicon and more.",
@ -713,7 +708,6 @@
"Avatar": "Avatar",
"Avatars": "Avatars",
"Avatar_changed_successfully": "Avatar changed successfully",
"Moderation_Avatar_reset_successfully": "Avatar reset successfully",
"Avatar_URL": "Avatar URL",
"Avatar_format_invalid": "Invalid Format. Only image type is allowed",
"Avatar_url_invalid_or_error": "The url provided is invalid or not accessible. Please try again, but with a different url.",
@ -1517,7 +1511,6 @@
"DDP_Rate_Limit_User_Interval_Time": "Limit by User: interval time",
"DDP_Rate_Limit_User_Requests_Allowed": "Limit by User: requests allowed",
"Deactivate": "Deactivate",
"Moderation_Deactivate_User": "Deactivate user",
"Decline": "Decline",
"Decode_Key": "Decode Key",
"default": "default",
@ -1532,7 +1525,6 @@
"Delete_Department?": "Delete Department?",
"Delete_File_Warning": "Deleting a file will delete it forever. This cannot be undone.",
"Delete_message": "Delete message",
"Moderation_Delete_all_messages": "Delete all messages",
"Delete_my_account": "Delete my account",
"Delete_Role_Warning": "This cannot be undone",
"Delete_Role_Warning_Community_Edition": "This cannot be undone. Note that it's not possible to create new custom roles in Community Edition",
@ -1664,7 +1656,6 @@
"Discussions_unavailable_for_federation": "Discussions are unavailable for Federated rooms",
"discussion-created": "{{message}}",
"Discussions": "Discussions",
"Moderation_Dismiss_reports": "Dismiss reports",
"Display": "Display",
"Display_avatars": "Display Avatars",
"Display_Avatars_Sidebar": "Display Avatars in Sidebar",
@ -1713,8 +1704,6 @@
"Markdown_Marked_SmartLists": "Enable Marked Smart Lists",
"Duplicate_private_group_name": "A Private Group with name '%s' exists",
"Markdown_Marked_Smartypants": "Enable Marked Smartypants",
"Moderation_Duplicate_messages": "Duplicated messages",
"Moderation_Duplicate_messages_warning": "Following may contain same messages sent in multiple rooms.",
"Duplicated_Email_address_will_be_ignored": "Duplicated email address will be ignored.",
"Markdown_Marked_Tables": "Enable Marked Tables",
"duplicated-account": "Duplicated account",
@ -3494,13 +3483,38 @@
"mobile-upload-file_description": "Permission to allow file upload on mobile devices",
"Mobile_Push_Notifications_Default_Alert": "Push Notifications Default Alert",
"Moderation": "Moderation",
"Moderation_View_reports": "View reports",
"Moderation_Show_reports": "Show reports",
"Moderation_Go_to_message": "Go to message",
"Moderation_Delete_message": "Delete message",
"Moderation_Dismiss_and_delete": "Dismiss and delete",
"Moderation_Delete_this_message": "Delete this message",
"Moderation_Message_context_header": "Message(s) from {{displayName}}",
"Moderation_Message_context_header": "Reported message(s)",
"Moderation_Message_deleted": "Message deleted and reports dismissed",
"Moderation_Messages_deleted": "Messages deleted and reports dismissed",
"Moderation_Action_View_reports": "View reported messages",
"Moderation_Hide_reports": "Hide reports",
"Moderation_Dismiss_all_reports": "Dismiss all reports",
"Moderation_Deactivate_User": "Deactivate user",
"Moderation_User_deactivated": "User deactivated",
"Moderation_Delete_all_messages": "Delete all messages",
"Moderation_Dismiss_reports": "Dismiss reports",
"Moderation_Duplicate_messages": "Duplicated messages",
"Moderation_Duplicate_messages_warning": "Following may contain same messages sent in multiple rooms.",
"Moderation_Report_date": "Report date",
"Moderation_Report_plural": "Reports",
"Moderation_Reported_message": "Reported message",
"Moderation_Reports_dismissed": "Reports dismissed",
"Moderation_Reports_dismissed_plural": "All reports dismissed",
"Moderation_Message_already_deleted": "Message is already deleted",
"Moderation_Reset_user_avatar": "Reset user avatar",
"Moderation_See_messages": "See messages",
"Moderation_Avatar_reset_success": "Avatar reset",
"Moderation_Dismiss_reports_confirm": "Reports will be deleted and the reported message won't be affected.",
"Moderation_Dismiss_all_reports_confirm": "All reports will be deleted and the reported messages won't be affected.",
"Moderation_Are_you_sure_you_want_to_delete_this_message": "This message will be permanently deleted from its respective room and the report will be dismissed.",
"Moderation_Are_you_sure_you_want_to_reset_the_avatar": "Resetting user avatar will permanently remove their current avatar.",
"Moderation_Are_you_sure_you_want_to_deactivate_this_user": "User will be unable to log in unless reactivated. All reported messages will be permanently deleted from their respective room.",
"Moderation_Are_you_sure_you_want_to_delete_all_reported_messages_from_this_user": "All reported messages from this user will be permanently deleted from their respective room and the report will be dismissed.",
"Moderation_User_deleted_warning": "The user who sent the message(s) no longer exists or has been removed.",
"Monday": "Monday",
"Mongo_storageEngine": "Mongo Storage Engine",
@ -3553,7 +3567,7 @@
"Name_Placeholder": "Please enter your name...",
"Navigation": "Navigation",
"Navigation_bar": "Navigation bar",
"Navigation_bar_description": "Introducing the navigation bar — a higher-level navigation designed to help users quickly find what they need. With its compact design and intuitive organization, this streamlined sidebar optimizes screen space while providing easy access to essential software features and sections.",
"Navigation_bar_description": "Introducing the navigation bar — a higher-level navigation designed to help users quickly find what they need. With its compact design and intuitive organization, this streamlined sidebar optimizes screen space while providing easy access to essential software features and sections.",
"Navigation_History": "Navigation History",
"Next": "Next",
"Never": "Never",
@ -3963,7 +3977,6 @@
"Pool": "Pool",
"Port": "Port",
"Post_as": "Post as",
"Moderation_Report_date": "Report date",
"Post_to": "Post to",
"Post_to_Channel": "Post to Channel",
"Post_to_s_as_s": "Post to <strong>%s</strong> as <strong>%s</strong>",
@ -4180,16 +4193,12 @@
"Reply_via_Email": "Reply via Email",
"ReplyTo": "Reply-To",
"Report": "Report",
"Moderation_Report_plural": "Reports",
"Report_Abuse": "Report Abuse",
"Report_exclamation_mark": "Report!",
"Report_has_been_sent": "Report has been sent",
"Report_Number": "Report Number",
"Report_this_message_question_mark": "Report this message?",
"Moderation_Reported_message": "Reported message",
"Reporting": "Reporting",
"Moderation_Reports_dismissed": "Reports dismissed",
"Moderation_Message_already_deleted": "Message is already deleted",
"Request": "Request",
"Request_seats": "Request Seats",
"Request_more_seats": "Request more seats.",
@ -4222,7 +4231,6 @@
"Reset_password": "Reset password",
"Reset_section_settings": "Restore defaults",
"Reset_TOTP": "Reset TOTP",
"Moderation_Reset_user_avatar": "Reset user avatar",
"reset-other-user-e2e-key": "Reset Other User E2E Key",
"Responding": "Responding",
"Response_description_post": "Empty bodies or bodies with an empty text property will simply be ignored. Non-200 responses will be retried a reasonable number of times. A response will be posted using the alias and avatar specified above. You can override these informations as in the example above.",
@ -4492,7 +4500,6 @@
"Secure_SaaS_solution": "Secure SaaS solution.",
"Security": "Security",
"See_documentation": "See documentation",
"Moderation_See_messages": "See messages",
"See_Paid_Plan": "See paid plan",
"See_Pricing": "See Pricing",
"See_full_profile": "See full profile",
@ -4641,7 +4648,7 @@
"Show_video": "Show video",
"Showing": "Showing",
"Showing_archived_results": "<p>Showing <b>%s</b> archived results</p>",
"Showing_current_of_total":"Showing {{current}} of {{total}}",
"Showing_current_of_total": "Showing {{current}} of {{total}}",
"Showing_online_users": "Showing: <b>{{total_showing}}</b>, Online: {{online}}, Total: {{total}} users",
"Showing_results": "<p>Showing <b>%s</b> results</p>",
"Showing_results_of": "Showing results %s - %s of %s",
@ -5968,4 +5975,4 @@
"Uninstall_grandfathered_app": "Uninstall {{appName}}?",
"App_will_lose_grandfathered_status": "**This {{context}} app will lose its grandfathered status.** \n \nWorkspaces on Community Edition can have up to {{limit}} {{context}} apps enabled. Grandfathered apps count towards the limit but the limit is not applied to them.",
"Theme_Appearence": "Theme Appearence"
}
}
Loading…
Cancel
Save