diff --git a/_locales/en/messages.json b/_locales/en/messages.json index cb565b89d..7a054b1e2 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -158,6 +158,7 @@ "cameraGrantAccessDescription": "{app_name} needs camera access to take photos and videos, or scan QR codes.", "cameraGrantAccessQr": "{app_name} needs camera access to scan QR codes", "cancel": "Cancel", + "changePasswordFail": "Failed to change password", "clear": "Clear", "clearAll": "Clear All", "clearDataAll": "Clear All Data", @@ -188,7 +189,7 @@ "communityInvitation": "Community Invitation", "communityJoin": "Join Community", "communityJoinDescription": "Are you sure you want to join {community_name}?", - "communityJoinError": "Failed to join {community_name}", + "communityJoinError": "Failed to join community", "communityJoinOfficial": "Or join one of these...", "communityJoined": "Joined Community", "communityJoinedAlready": "You are already a member of this community.", @@ -277,6 +278,7 @@ "deleteMessagesDescriptionDevice": "Are you sure you want to delete these messages from this device only?", "deleteMessagesDescriptionEveryone": "Are you sure you want to delete these messages for everyone?", "deleteMessagesFailed": "{count, plural, one {Failed to delete message} other {Failed to delete messages}}", + "deleteafterMessageDeletionStandardisationmessageDeletionForbidden": "You don’t have permission to delete others’ messages", "deleting": "Deleting", "developerToolsToggle": "Toggle Developer Tools", "dictationStart": "Start Dictation...", @@ -300,8 +302,8 @@ "disappearingMessagesOnlyAdmins": "Only group admins can change this setting.", "disappearingMessagesSent": "Sent", "disappearingMessagesSet": "{name} has set messages to disappear {time} after they have been {disappearing_messages_type}.", - "disappearingMessagesTimer": "Timer", "disappearingMessagesSetYou": "You set messages to disappear {time} after they have been {disappearing_messages_type}.", + "disappearingMessagesTimer": "Timer", "disappearingMessagesTurnedOff": "{name} has turned disappearing messages off. Messages they send will no longer disappear.", "disappearingMessagesTurnedOffYou": "You turned off disappearing messages. Messages you send will no longer disappear.", "disappearingMessagesTypeRead": "read", @@ -391,7 +393,19 @@ "groupMemberLeftTwo": "{name} and {other_name} left the group.", "groupMemberMoreNew": "{name} and {count} others joined the group.", "groupMemberNew": "{name} joined the group.", + "groupMemberNewHistory": "{name} was invited to join the group. Chat history was shared.", + "groupMemberNewHistoryMultiple": "{name} and {count} others were invited to join the group. Chat history was shared.", + "groupMemberNewHistoryTwo": "{name} and {other_name} were invited to join the group. Chat history was shared.", + "groupMemberNewMultiple": "{name} and {count} others were invited to join the group.", + "groupMemberNewTwo": "{name} and {other_name} were invited to join the group.", + "groupMemberNewYouHistory": " {name} was invited to join the group. Chat history was shared.", + "groupMemberNewYouHistoryMultiple": "You and {count} others were invited to join the group. Chat history was shared.", + "groupMemberNewYouHistoryTwo": "You and {name} were invited to join the group. Chat history was shared.", + "groupMemberNewYouMultiple": "You and {count} others were invited to join the group.", + "groupMemberNewYouTwo": "You and {name} were invited to join the group.", "groupMemberTwoNew": "{name} and {other_name} joined the group.", + "groupMemberYouAndMoreNew": "You and {count} others joined the group.", + "groupMemberYouAndOtherNew": "You and {other_name} joined the group.", "groupMemberYouLeft": "You left the group.", "groupMembers": "Group Members", "groupMembersNone": "There are no other members in this group.", @@ -404,6 +418,8 @@ "groupNoMessages": "You have no messages from {group_name}. Send a message to start the conversation!", "groupOnlyAdmin": "You are the only admin in {group_name}.

Group members and settings cannot be changed without an admin.", "groupPromotedYou": "You were promoted to Admin.", + "groupPromotedYouMultiple": "You and {count} others were promoted to Admin.", + "groupPromotedYouTwo": "You and {name} were promoted to Admin.", "groupRemoveDescription": "Would you like to remove {name} from {group_name}?", "groupRemoveMessages": "Remove user and their messages", "groupRemoveMoreDescription": "Would you like to remove {name} and {count} others from {group_name}?", @@ -415,6 +431,8 @@ "groupRemovedMore": "{name} and {count} others were removed from the group.", "groupRemovedTwo": "{name} and {other_name} were removed from the group.", "groupRemovedYou": "You were removed from {group_name}.", + "groupRemovedYouMultiple": "You and {count} others were removed from the group.", + "groupRemovedYouTwo": "You and {other_name} were removed from the group.", "groupSetDisplayPicture": "Set Group Display Picture", "groupUnknown": "Unknown Group", "groupUpdated": "Group updated", @@ -560,6 +578,7 @@ "notificationsVibrate": "Vibrate", "off": "Off", "okay": "Okay", + "on": "On", "onboardingAccountCreate": "Create account", "onboardingAccountCreated": "Account Created", "onboardingAccountExists": "I have an account", @@ -666,6 +685,7 @@ "recoveryPasswordWarningSendDescription": "This is your recovery password. If you send it to someone they'll have full access to your account.", "redo": "Redo", "remove": "Remove", + "removePasswordFail": "Failed to remove password", "reply": "Reply", "resend": "Resend", "resolving": "Loading country information...", diff --git a/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx b/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx index 67bb5b053..5ed237ae8 100644 --- a/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx +++ b/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx @@ -1,5 +1,7 @@ -import { useConversationsUsernameWithQuoteOrFullPubkey } from '../../../../hooks/useParamSelector'; -import { arrayContainsUsOnly } from '../../../../models/message'; +import { + getKickedGroupUpdateStr, + getLeftGroupUpdateChangeStr, +} from '../../../../models/groupUpdate'; import { PropsForGroupUpdate, PropsForGroupUpdateType, @@ -12,51 +14,38 @@ import { NotificationBubble } from './notification-bubble/NotificationBubble'; // This component is used to display group updates in the conversation view. const ChangeItemJoined = (added: Array): string => { + const groupName = useSelectedNicknameOrProfileNameOrShortenedPubkey(); + if (!added.length) { - throw new Error('Group update add is missing contacts'); + throw new Error('Group update added is missing details'); } - const names = useConversationsUsernameWithQuoteOrFullPubkey(added); - return window.i18n('groupMemberNew', { - name: names.join(', '), - }); + // this is not ideal, but also might not be changed as part of Strings but, + // we return a string containing style tags ( etc) here, and a SessionHtmlRenderer is going + // to render them correctly. + return getLeftGroupUpdateChangeStr(added, groupName, false); }; const ChangeItemKicked = (kicked: Array): string => { if (!kicked.length) { - throw new Error('Group update kicked is missing contacts'); + throw new Error('Group update kicked is missing details'); } - const names = useConversationsUsernameWithQuoteOrFullPubkey(kicked); const groupName = useSelectedNicknameOrProfileNameOrShortenedPubkey(); - - if (arrayContainsUsOnly(kicked)) { - return window.i18n('groupRemovedYou', { group_name: groupName }); - } - - // TODO - support bold - return kicked.length === 1 - ? window.i18n('groupRemoved', { name: names[0] }) - : kicked.length === 2 - ? window.i18n('groupRemovedTwo', { name: names[0], other_name: names[1] }) - : window.i18n('groupRemovedMore', { name: names[0], count: names.length }); + // this is not ideal, but also might not be changed as part of Strings but, + // we return a string containing style tags ( etc) here, and a SessionHtmlRenderer is going + // to render them correctly. + return getKickedGroupUpdateStr(kicked, groupName, false); }; const ChangeItemLeft = (left: Array): string => { - if (!left.length) { - throw new Error('Group update remove is missing contacts'); - } - - const names = useConversationsUsernameWithQuoteOrFullPubkey(left); + const groupName = useSelectedNicknameOrProfileNameOrShortenedPubkey(); - if (arrayContainsUsOnly(left)) { - return window.i18n('groupMemberYouLeft'); + if (!left.length) { + throw new Error('Group update left is missing details'); } - - // TODO - support bold - return left.length === 1 - ? window.i18n('groupMemberLeft', { name: names[0] }) - : left.length === 2 - ? window.i18n('groupMemberLeftTwo', { name: names[0], other_name: names[1] }) - : window.i18n('groupMemberLeftMore', { name: names[0], count: names.length }); + // this is not ideal, but also might not be changed as part of Strings but, + // we return a string containing style tags ( etc) here, and a SessionHtmlRenderer is going + // to render them correctly. + return getLeftGroupUpdateChangeStr(left, groupName, false); }; const ChangeItem = (change: PropsForGroupUpdateType): string => { diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 4c02a547f..d7a292dc4 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -2052,10 +2052,7 @@ export class ConversationModel extends Backbone.Model { const roomInfo = OpenGroupData.getV2OpenGroupRoom(groupUrl); if (!roomInfo || !roomInfo.serverPublicKey) { - ToastUtils.pushToastError( - 'no-sogs-matching', - window.i18n('communityJoinError', { community_name: groupUrl }) - ); + ToastUtils.pushToastError('no-sogs-matching', window.i18n('communityJoinError')); window?.log?.error('Could not find room with matching server url', groupUrl); throw new Error(`Could not find room with matching server url: ${groupUrl}`); } diff --git a/ts/models/groupUpdate.ts b/ts/models/groupUpdate.ts new file mode 100644 index 000000000..168001634 --- /dev/null +++ b/ts/models/groupUpdate.ts @@ -0,0 +1,104 @@ +import { getConversationController } from '../session/conversations'; +import { UserUtils } from '../session/utils'; + +// to remove after merge with groups +function usAndXOthers(arr: Array) { + const us = UserUtils.getOurPubKeyStrFromCache(); + + if (arr.includes(us)) { + return { us: true, others: arr.filter(m => m !== us) }; + } + return { us: false, others: arr }; +} + +export function getKickedGroupUpdateStr( + kicked: Array, + groupName: string, + stripTags: boolean +) { + const { others, us } = usAndXOthers(kicked); + const othersNames = others.map( + getConversationController().getContactProfileNameOrShortenedPubKey + ); + if (us) { + switch (others.length) { + case 0: + return window.i18n('groupRemovedYou', { group_name: groupName }); + case 1: + return window.i18n('groupRemovedYouTwo', { other_name: othersNames[0] }); + default: + return window.i18n('groupRemovedYouMultiple', { count: othersNames.length }); + } + } + switch (others.length) { + case 0: + throw new Error('kicked without anyone in it.'); + case 1: + return window.i18n('groupRemoved', { name: othersNames[0] }); + case 2: + return window.i18n('groupRemovedTwo', { + name: othersNames[0], + other_name: othersNames[1], + }); + default: + return window.i18n('groupRemovedMore', { + name: others[0], + count: othersNames.length - 1, + }); + } +} + +export function getLeftGroupUpdateChangeStr( + left: Array, + _groupName: string, + stripTags: boolean +) { + const { others, us } = usAndXOthers(left); + + if (left.length !== 1) { + throw new Error('left.length should never be more than 1'); + } + + return us + ? window.i18n('groupMemberYouLeft') + : window.i18n('groupMemberLeft', { + name: getConversationController().getContactProfileNameOrShortenedPubKey(others[0]), + }); +} + +export function getJoinedGroupUpdateChangeStr( + joined: Array, + _groupName: string, + stripTags: boolean +) { + const { others, us } = usAndXOthers(joined); + const othersNames = others.map( + getConversationController().getContactProfileNameOrShortenedPubKey + ); + if (us) { + switch (others.length) { + case 0: + return window.i18n('groupMemberNew', { name: window.i18n('you') }); + case 1: + return window.i18n('groupMemberYouAndOtherNew', { other_name: othersNames[0] }); + default: + return window.i18n('groupMemberYouAndMoreNew', { count: othersNames.length }); + } + } + switch (others.length) { + case 0: + throw new Error('joined without anyone in it.'); + case 1: + return window.i18n('groupMemberNew', { name: othersNames[0] }); + case 2: + return window.i18n('groupMemberTwoNew', { + name: othersNames[0], + other_name: othersNames[1], + }); + default: + return window.i18n('groupMemberMoreNew', { + name: others[0], + count: othersNames.length - 1, + }); + } +} diff --git a/ts/models/message.ts b/ts/models/message.ts index bbb4c27c7..be8a4c27a 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -2,16 +2,7 @@ import Backbone from 'backbone'; import autoBind from 'auto-bind'; import filesize from 'filesize'; -import { - cloneDeep, - debounce, - isEmpty, - size as lodashSize, - map, - partition, - pick, - uniq, -} from 'lodash'; +import { cloneDeep, debounce, isEmpty, size as lodashSize, partition, pick, uniq } from 'lodash'; import { SignalService } from '../protobuf'; import { getMessageQueue } from '../session'; import { getConversationController } from '../session/conversations'; @@ -95,25 +86,14 @@ import { ConversationModel } from './conversation'; import { READ_MESSAGE_STATE } from './conversationAttributes'; import { ConversationInteractionStatus, ConversationInteractionType } from '../interactions/types'; import { LastMessageStatusType } from '../state/ducks/types'; +import { + getJoinedGroupUpdateChangeStr, + getKickedGroupUpdateStr, + getLeftGroupUpdateChangeStr, +} from './groupUpdate'; // tslint:disable: cyclomatic-complexity -/** - * @returns true if the array contains only a single item being 'You', 'you' or our device pubkey - */ -export function arrayContainsUsOnly(arrayToCheck: Array | undefined) { - return ( - arrayToCheck && - arrayToCheck.length === 1 && - (arrayToCheck[0] === UserUtils.getOurPubKeyStrFromCache() || - arrayToCheck[0].toLowerCase() === 'you') - ); -} - -export function arrayContainsOneItemOnly(arrayToCheck: Array | undefined) { - return arrayToCheck && arrayToCheck.length === 1; -} - export class MessageModel extends Backbone.Model { constructor(attributes: MessageAttributesOptionals & { skipTimerInit?: boolean }) { const filledAttrs = fillMessageAttributesWithDefaults(attributes); @@ -1289,26 +1269,9 @@ export class MessageModel extends Backbone.Model { if (groupUpdate) { const groupName = this.getConversation()?.getNicknameOrRealUsernameOrPlaceholder() || window.i18n('unknown'); - if (arrayContainsUsOnly(groupUpdate.kicked)) { - return window.i18n('groupRemovedYou', { group_name: groupName }); - } - - if (arrayContainsUsOnly(groupUpdate.left)) { - return window.i18n('groupMemberYouLeft'); - } - - if (groupUpdate.left && groupUpdate.left.length === 1) { - return window.i18n('groupMemberLeft', { - name: getConversationController().getContactProfileNameOrShortenedPubKey( - groupUpdate.left[0] - ), - }); - } - const messages = []; - - if (!groupUpdate.name && !groupUpdate.joined && !groupUpdate.kicked && !groupUpdate.kicked) { - return window.i18n('groupUpdated'); + if (groupUpdate.left) { + return getLeftGroupUpdateChangeStr(groupUpdate.left, groupName, true); } if (groupUpdate.name) { @@ -1316,28 +1279,15 @@ export class MessageModel extends Backbone.Model { } if (groupUpdate.joined && groupUpdate.joined.length) { - const names = groupUpdate.joined.map( - getConversationController().getContactProfileNameOrShortenedPubKey - ); - - messages.push(window.i18n('groupMemberNew', { name: names.join(', ') })); - - return messages.join(' '); + return getJoinedGroupUpdateChangeStr(groupUpdate.joined, groupName, true); } - if (groupUpdate.kicked && groupUpdate.kicked.length) { - const names = map( - groupUpdate.kicked, - getConversationController().getContactProfileNameOrShortenedPubKey - ); - - if (names.length > 1) { - messages.push(window.i18n('multipleKickedFromTheGroup', { name: names.join(', ') })); - } else { - messages.push(window.i18n('groupRemoved', { name: names[0] })); - } + if (groupUpdate.kicked?.length) { + return getKickedGroupUpdateStr(groupUpdate.kicked, groupName, true); } - return messages.join(' '); + window.log.warn('did not build a specific change for getDescription of ', groupUpdate); + + return window.i18n('groupUpdated'); } if (this.isGroupInvitation()) { diff --git a/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts b/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts index 68b2cb408..bc3190e9b 100644 --- a/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts +++ b/ts/session/apis/open_group_api/opengroupV2/JoinOpenGroupV2.ts @@ -96,9 +96,7 @@ async function joinOpenGroupV2( if (!conversation) { window?.log?.warn('Failed to join open group v2'); // TODO - Check that this is the room name - throw new Error( - window.i18n('communityJoinError', { community_name: roomId || window.i18n('unknown') }) - ); + throw new Error(window.i18n('communityJoinError')); } // here we managed to connect to the group. @@ -183,19 +181,10 @@ export async function joinOpenGroupV2WithUIEvents( } if (showToasts) { // TODO - Check that this is the room name - ToastUtils.pushToastError( - 'communityJoinError', - window.i18n('communityJoinError', { - community_name: parsedRoom.roomId || window.i18n('unknown'), - }) - ); + ToastUtils.pushToastError('communityJoinError', window.i18n('communityJoinError')); } if (errorHandler) { - errorHandler( - window.i18n('communityJoinError', { - community_name: parsedRoom.roomId || window.i18n('unknown'), - }) - ); + errorHandler(window.i18n('communityJoinError')); } uiCallback?.({ loadingState: 'failed', conversationKey: conversationID }); @@ -203,15 +192,10 @@ export async function joinOpenGroupV2WithUIEvents( window?.log?.warn('got error while joining open group:', error.message); if (showToasts) { // TODO - Check that this is the room name - ToastUtils.pushToastError( - 'communityJoinError', - window.i18n('communityJoinError', { community_name: window.i18n('unknown') }) - ); + ToastUtils.pushToastError('communityJoinError', window.i18n('communityJoinError')); } if (errorHandler) { - errorHandler( - window.i18n('communityJoinError', { community_name: window.i18n('unknown') }) - ); // we don't have a parsed room, so let's show the whole url in this case + errorHandler(window.i18n('communityJoinError')); } uiCallback?.({ loadingState: 'failed', conversationKey: null }); } diff --git a/ts/session/utils/Toast.tsx b/ts/session/utils/Toast.tsx index cb35fd754..7b7596054 100644 --- a/ts/session/utils/Toast.tsx +++ b/ts/session/utils/Toast.tsx @@ -110,7 +110,10 @@ export function pushUserUnbanFailure() { } export function pushMessageDeleteForbidden() { - pushToastError('messageDeletionForbidden', window.i18n('messageDeletionForbidden')); + pushToastError( + 'messageDeletionForbidden', + window.i18n('deleteafterMessageDeletionStandardisationmessageDeletionForbidden') + ); } export function pushUnableToCall() { @@ -139,7 +142,6 @@ export function pushedMissedCallCauseOfPermission(conversationName: string) { ); } - export function pushVideoCallPermissionNeeded() { pushToastInfo( 'videoCallPermissionNeeded', @@ -187,16 +189,16 @@ export function pushDeleted() { } export function pushCannotRemoveCreatorFromGroup() { - pushToastWarning('cannotRemoveCreatorFromGroup', window.i18n('adminCannotBeRemoved')); + pushToastWarning('adminCannotBeRemoved', window.i18n('adminCannotBeRemoved')); } export function pushFailedToAddAsModerator() { - pushToastWarning('failedToAddAsModerator', window.i18n('adminPromotionFailed')); + pushToastWarning('adminPromotionFailed', window.i18n('adminPromotionFailed')); } export function pushFailedToRemoveFromModerator(name: string) { pushToastWarning( - 'failedToRemoveFromModerator', + 'adminRemoveFailed', window.i18n('adminRemoveFailed', { name, }) diff --git a/ts/window.d.ts b/ts/window.d.ts index aca545956..36701d6a6 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -4,10 +4,7 @@ import {} from 'styled-components/cssprop'; import { Store } from '@reduxjs/toolkit'; import { Persistor } from 'redux-persist/es/types'; -import { LocalizerType } from './types/Util'; - import { ConversationCollection } from './models/conversation'; -import type { StateType } from './state/reducer'; import { PrimaryColorStateType, ThemeStateType } from './themes/constants/colors'; import type { GetMessageArgs, LocalizerDictionary, LocalizerToken } from './types/Localizer'; import type { Locale } from './util/i18n';