diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 7a054b1e2..c874fc15f 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -30,7 +30,11 @@ "adminRemoveAsAdmin": "Remove as Admin", "adminRemoveCommunityNone": "There are no Admins in this Community.", "adminRemoveFailed": "Failed to remove {name} as Admin.", + "adminRemoveFailedMultiple": "Failed to remove {name} and {count} others as Admin.", + "adminRemoveFailedOther": "Failed to remove {name} and {other_name} as Admin.", "adminRemovedUser": "{name} was removed as Admin.", + "adminRemovedUserMultiple": "{name} and {count} others were removed as Admin.", + "adminRemovedUserOther": "{name} and {other_name} were removed as Admin.", "adminSendingPromotion": "Sending admin promotion", "adminSettings": "Admin Settings", "adminTwoPromotedToAdmin": "{name} and {other_name} were promoted to Admin.", @@ -67,6 +71,7 @@ "attachmentsDownload": "Download Attachment", "attachmentsDuration": "Duration:", "attachmentsErrorLoad": "Error attaching file", + "attachmentsErrorMediaSelection": "Failed to select attachment", "attachmentsErrorNoApp": "Can't find an app to select media.", "attachmentsErrorNotSupported": "This file type is not supported.", "attachmentsErrorNumber": "Unable to send more than 32 image and video files at once.", @@ -143,7 +148,7 @@ "callsPermissionsRequiredDescription": "You can enable the \"Voice and Video Calls\" permission in Privacy Settings.", "callsReconnecting": "Reconnecting…", "callsRinging": "Ringing...", - "callsSessionCall": "Session Call", + "callsSessionCall": "{app_name} Call", "callsSettings": "Calls (Beta)", "callsVoiceAndVideo": "Voice and Video Calls", "callsVoiceAndVideoBeta": "Voice and Video Calls (Beta)", @@ -243,7 +248,7 @@ "create": "Create", "cut": "Cut", "databaseErrorGeneric": "A database error occurred.

Export your application logs to share for troubleshooting. If this is unsuccessful, reinstall {app_name} and restore your account.

Warning: This will result in loss of all messages, attachments, and account data older than two weeks.", - "databaseErrorTimeout": "We've noticed {app_name} is taking a long time to start.

You can continue to wait, export your device logs to share for troubleshooting, or try restarting Session.", + "databaseErrorTimeout": "We've noticed {app_name} is taking a long time to start.

You can continue to wait, export your device logs to share for troubleshooting, or try restarting {app_name}.", "databaseErrorUpdate": "Your app database is incompatible with this version of {app_name}. Reinstall the app and restore your account to generate a new database and continue using {app_name}.

Warning: This will result in the loss of all messages and attachments older than two weeks.", "databaseOptimizing": "Optimizing Database", "debugLog": "Debug Log", @@ -261,6 +266,8 @@ "deleteAfterLegacyDisappearingMessagesLegacy": "Legacy", "deleteAfterLegacyDisappearingMessagesOriginal": "Original version of disappearing messages.", "deleteAfterLegacyDisappearingMessagesTheyChangedTimer": "{name} set the disappearing message timer to {time}", + "deleteAfterLegacyGroupsGroupCreation": "Please wait while the group is created...", + "deleteAfterLegacyGroupsGroupUpdateErrorTitle": "Failed to Update Group", "deleteMessage": "Delete Message", "deleteMessageConfirm": "Are you sure you want to delete this message?", "deleteMessageDeleted": "Message deleted", @@ -325,27 +332,27 @@ "downloading": "Downloading...", "draft": "Draft", "edit": "Edit", - "emojiAndSymbols": "Emoji & Symbols", + "emojiAndSymbols": "Emoji and Symbols", "emojiCategoryActivities": "Activities", - "emojiCategoryAnimals": "Animals & Nature", + "emojiCategoryAnimals": "Animals and Nature", "emojiCategoryFlags": "Flags", - "emojiCategoryFood": "Food & Drink", + "emojiCategoryFood": "Food and Drink", "emojiCategoryObjects": "Objects", "emojiCategoryRecentlyUsed": "Recently Used", - "emojiCategorySmileys": "Smileys & People", + "emojiCategorySmileys": "Smileys and People", "emojiCategorySymbols": "Symbols", - "emojiCategoryTravel": "Travel & Places", + "emojiCategoryTravel": "Travel and Places", "emojiReactsClearAll": "Are you sure you want to clear all {emoji}?", "emojiReactsCoolDown": "Slow down! You've sent too many emoji reacts. Try again soon", "emojiReactsCountOthers": "{count, plural, one {And # other has reacted {emoji} to this message.} other {And # others have reacted {emoji} to this message.}}", "emojiReactsHoverNameDesktop": "{name} reacted with ", - "emojiReactsHoverTwoNameDesktop": "{name} & {other_name} reacted with ", + "emojiReactsHoverTwoNameDesktop": "{name} and {other_name} reacted with ", "emojiReactsHoverTwoNameMultipleDesktop": "{name}, {other_name} and {count} others reacted with ", "emojiReactsHoverTwoNameOneDesktop": "{name}, {other_name} and 1 other reacted with ", "emojiReactsHoverYouDesktop": "You reacted with ", - "emojiReactsHoverYouNameDesktop": "You & {name} reacted with ", - "emojiReactsHoverYouNameMultipleDesktop": "You, {name} & {count} others reacted with ", - "emojiReactsHoverYouNameOneDesktop": "You, {name} & 1 other reacted with ", + "emojiReactsHoverYouNameDesktop": "You and {name} reacted with ", + "emojiReactsHoverYouNameMultipleDesktop": "You, {name} and {count} others reacted with ", + "emojiReactsHoverYouNameOneDesktop": "You, {name} and 1 other reacted with ", "emojiReactsNotification": "Reacted to your message {emoji}", "enable": "Enable", "errorConnection": "Please check your internet connection and try again.", @@ -381,8 +388,10 @@ "groupInviteSending": "Sending invite", "groupInviteSent": "Invite sent", "groupInviteSuccessful": "Group invite successful", - "groupInviteVersion": "Users must have version {version} or higher to receive invitations", + "groupInviteVersion": "Users must have the latest release to receive invitations", "groupInviteYou": "You were invited to join the group.", + "groupInviteYouAndMoreNew": "You and {count} others were invited to join the group.", + "groupInviteYouAndOtherNew": "You and {other_name} were invited to join the group.", "groupLeave": "Leave Group", "groupLeaveDescription": "Are you sure you want to leave {group_name}?", "groupLeaveDescriptionAdmin": "Are you sure you want to leave {group_name}? This will deactivate the group for all members.", @@ -392,7 +401,7 @@ "groupMemberLeftMore": "{name} and {count} others left the group.", "groupMemberLeftTwo": "{name} and {other_name} left the group.", "groupMemberMoreNew": "{name} and {count} others joined the group.", - "groupMemberNew": "{name} joined the group.", + "groupMemberNew": "{name} was invited to join 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.", @@ -401,12 +410,11 @@ "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.", + "groupMemberNewYouMultiple": "You and {count} others joined the group.", + "groupMemberNewYouOther": "You and {other_name} joined 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.", + "groupMemberYouNew": "You joined the group.", "groupMembers": "Group Members", "groupMembersNone": "There are no other members in this group.", "groupName": "Group Name", @@ -582,8 +590,8 @@ "onboardingAccountCreate": "Create account", "onboardingAccountCreated": "Account Created", "onboardingAccountExists": "I have an account", - "onboardingBackAccountCreation": "You cannot go back further. In order to cancel your account creation, Session needs to quit.", - "onboardingBackLoadAccount": "You cannot go back further. In order to stop loading your account, Session needs to quit.", + "onboardingBackAccountCreation": "You cannot go back further. In order to cancel your account creation, {app_name} needs to quit.", + "onboardingBackLoadAccount": "You cannot go back further. In order to stop loading your account, {app_name} needs to quit.", "onboardingBubbleCreatingAnAccountIsEasy": "Creating an account is instant, free, and anonymous ", "onboardingBubbleNoPhoneNumber": "You don't even need a phone number to sign up.", "onboardingBubblePrivacyInYourPocket": "Privacy in your pocket.", @@ -670,6 +678,7 @@ "recoveryPasswordBannerTitle": "Save your recovery password", "recoveryPasswordDescription": "Use your recovery password to load your account on new devices.

Your account cannot be recovered without your recovery password. Make sure it's stored somewhere safe and secure — and don't share it with anyone.", "recoveryPasswordEnter": "Enter your recovery password", + "recoveryPasswordErrorLoad": "An error occurred when trying to load your recovery password.

Please export your logs, then upload the file though Session's Help Desk to help resolve this issue.", "recoveryPasswordErrorMessageGeneric": "Please check your recovery password and try again.", "recoveryPasswordErrorMessageIncorrect": "Some of the words in your Recovery Password are incorrect. Please check and try again.", "recoveryPasswordErrorMessageShort": "The Recovery Password you entered is not long enough. Please check and try again.", @@ -729,7 +738,7 @@ "set": "Set", "settingsRestartDescription": "You must restart {app_name} to apply your new settings.", "share": "Share", - "shareAccountIdDescription": "Invite your friend to chat with you on Session by sharing your Account ID with them.", + "shareAccountIdDescription": "Invite your friend to chat with you on {app_name} by sharing your Account ID with them.", "shareAccountIdDescriptionCopied": "Share with your friends wherever you usually speak with them — then move the conversation here.", "shareExtensionDatabaseError": "There is an issue opening the database. Please restart the app and try again.", "shareToSession": "Share to {app_Name}", @@ -771,4 +780,4 @@ "window": "Window", "yes": "Yes", "you": "You" -} +} \ No newline at end of file diff --git a/ts/components/basic/SessionHTMLRenderer.tsx b/ts/components/basic/SessionHTMLRenderer.tsx index 390857523..073ada1c3 100644 --- a/ts/components/basic/SessionHTMLRenderer.tsx +++ b/ts/components/basic/SessionHTMLRenderer.tsx @@ -17,7 +17,6 @@ export const SessionHtmlRenderer = ({ tag = 'div', key, html, className }: Recei return createElement(tag, { key, className, - dangerouslySetInnerHTML: { __html: clean }, }); }; diff --git a/ts/components/conversation/SessionConversation.tsx b/ts/components/conversation/SessionConversation.tsx index f4e5d9f46..9febbc841 100644 --- a/ts/components/conversation/SessionConversation.tsx +++ b/ts/components/conversation/SessionConversation.tsx @@ -398,7 +398,7 @@ export class SessionConversation extends Component { }); if (blob.blob.size > MAX_ATTACHMENT_FILESIZE_BYTES) { - ToastUtils.pushFileSizeErrorAsByte(MAX_ATTACHMENT_FILESIZE_BYTES); + ToastUtils.pushFileSizeErrorAsByte(); return; } } catch (error) { diff --git a/ts/components/conversation/SessionRecording.tsx b/ts/components/conversation/SessionRecording.tsx index f6f5073b1..82ac335f7 100644 --- a/ts/components/conversation/SessionRecording.tsx +++ b/ts/components/conversation/SessionRecording.tsx @@ -291,7 +291,7 @@ export class SessionRecording extends Component { // Is the audio file > attachment filesize limit if (this.audioBlobMp3.size > MAX_ATTACHMENT_FILESIZE_BYTES) { - ToastUtils.pushFileSizeErrorAsByte(MAX_ATTACHMENT_FILESIZE_BYTES); + ToastUtils.pushFileSizeErrorAsByte(); return; } diff --git a/ts/components/dialog/ModeratorsAddDialog.tsx b/ts/components/dialog/ModeratorsAddDialog.tsx index 4b774c8ec..1c285feb6 100644 --- a/ts/components/dialog/ModeratorsAddDialog.tsx +++ b/ts/components/dialog/ModeratorsAddDialog.tsx @@ -50,8 +50,11 @@ export const AddModeratorsDialog = (props: Props) => { ToastUtils.pushFailedToAddAsModerator(); } else { + const userDisplayName = + getConversationController().get(pubkey.key)?.getNicknameOrRealUsernameOrPlaceholder() || + window.i18n('unknown'); window?.log?.info(`${pubkey.key} added as moderator...`); - ToastUtils.pushUserAddedToModerators(); + ToastUtils.pushUserAddedToModerators(userDisplayName); // clear input box setInputBoxValue(''); diff --git a/ts/components/dialog/ModeratorsRemoveDialog.tsx b/ts/components/dialog/ModeratorsRemoveDialog.tsx index 9e413fdcc..64d01ca58 100644 --- a/ts/components/dialog/ModeratorsRemoveDialog.tsx +++ b/ts/components/dialog/ModeratorsRemoveDialog.tsx @@ -25,22 +25,27 @@ async function removeMods(convoId: string, modsToRemove: Array) { return false; } window?.log?.info(`asked to remove moderators: ${modsToRemove}`); - + const modsToRemovePubkey = compact(modsToRemove.map(m => PubKey.from(m))); + const modsToRemoveNames = modsToRemovePubkey.map( + m => + getConversationController().get(m.key)?.getNicknameOrRealUsernameOrPlaceholder() || + window.i18n('unknown') + ); try { const convo = getConversationController().get(convoId); const roomInfos = convo.toOpenGroupV2(); - const modsToRemovePubkey = compact(modsToRemove.map(m => PubKey.from(m))); + const res = await sogsV3RemoveAdmins(modsToRemovePubkey, roomInfos); if (!res) { window?.log?.warn('failed to remove moderators:', res); - ToastUtils.pushFailedToRemoveFromModerator(); + ToastUtils.pushFailedToRemoveFromModerator(modsToRemoveNames); return false; } window?.log?.info(`${modsToRemove} removed from moderators...`); - ToastUtils.pushUserRemovedFromModerators(); + ToastUtils.pushUserRemovedFromModerators(modsToRemoveNames); return true; } catch (e) { window?.log?.error('Got error while removing moderator:', e); diff --git a/ts/interactions/messageInteractions.ts b/ts/interactions/messageInteractions.ts index 56fd75d0d..7da1ec4b4 100644 --- a/ts/interactions/messageInteractions.ts +++ b/ts/interactions/messageInteractions.ts @@ -78,10 +78,10 @@ export async function removeSenderFromModerator(sender: string, convoId: string) if (!res) { window?.log?.warn('failed to remove moderator:', res); - ToastUtils.pushFailedToRemoveFromModerator(userDisplayName); + ToastUtils.pushFailedToRemoveFromModerator([userDisplayName]); } else { window?.log?.info(`${pubKeyToRemove.key} removed from moderators...`); - ToastUtils.pushUserRemovedFromModerators(userDisplayName); + ToastUtils.pushUserRemovedFromModerators([userDisplayName]); } } catch (e) { window?.log?.error('Got error while removing moderator:', e); diff --git a/ts/models/groupUpdate.ts b/ts/models/groupUpdate.ts index ae5de957a..4f5a87aea 100644 --- a/ts/models/groupUpdate.ts +++ b/ts/models/groupUpdate.ts @@ -89,9 +89,9 @@ export function getJoinedGroupUpdateChangeStr( case 0: return getString('groupMemberNew', { name: window.i18n('you') }); case 1: - return getString('groupMemberYouAndOtherNew', { other_name: othersNames[0] }); + return getString('groupMemberNewYouOther', { other_name: othersNames[0] }); default: - return getString('groupMemberYouAndMoreNew', { count: othersNames.length }); + return getString('groupMemberNewYouMultiple', { count: othersNames.length }); } } switch (others.length) { diff --git a/ts/session/utils/Toast.tsx b/ts/session/utils/Toast.tsx index 7b7596054..c40171a6d 100644 --- a/ts/session/utils/Toast.tsx +++ b/ts/session/utils/Toast.tsx @@ -35,6 +35,12 @@ export function pushToastInfo( ); } +/** + * We are rendering a toast. A toast is only rendering a string and no html at all. + * We have to strip the html tags from the strings we are given. + */ +const getStrippedI18n = window.i18n.stripped; + export function pushToastSuccess(id: string, title: string, description?: string) { toast.success( , @@ -44,84 +50,72 @@ export function pushToastSuccess(id: string, title: string, description?: string export function pushLoadAttachmentFailure(message?: string) { if (message) { - pushToastError('unableToLoadAttachment', `${window.i18n('attachmentsErrorLoad')} ${message}`); + pushToastError( + 'unableToLoadAttachment', + `${getStrippedI18n('attachmentsErrorLoad')} ${message}` + ); } else { - pushToastError('unableToLoadAttachment', window.i18n('attachmentsErrorLoad')); + pushToastError('unableToLoadAttachment', getStrippedI18n('attachmentsErrorLoad')); } } -export function pushFileSizeError(limit: number, units: string) { - pushToastError( - 'fileSizeWarning', - window.i18n('attachmentsErrorSize'), - `Max size: ${limit} ${units}` - ); -} - -export function pushFileSizeErrorAsByte(bytesCount: number) { - const units = ['kB', 'MB', 'GB']; - let u = -1; - let limit = bytesCount; - do { - limit /= 1000; - u += 1; - } while (limit >= 1000 && u < units.length - 1); - pushFileSizeError(limit, units[u]); +export function pushFileSizeErrorAsByte() { + pushToastError('fileSizeWarning', getStrippedI18n('attachmentsErrorSize')); } export function pushMultipleNonImageError() { - pushToastError('cannotMixImageAndNonImageAttachments', window.i18n('attachmentsErrorTypes')); + pushToastError('attachmentsErrorTypes', getStrippedI18n('attachmentsErrorTypes')); } export function pushCannotMixError() { - pushToastError('oneNonImageAtATimeToast', window.i18n('attachmentsErrorTypes')); + pushToastError('attachmentsErrorTypes', getStrippedI18n('attachmentsErrorTypes')); } export function pushMaximumAttachmentsError() { - pushToastError('maximumAttachments', window.i18n('attachmentsErrorNumber')); + pushToastError('attachmentsErrorNumber', getStrippedI18n('attachmentsErrorNumber')); } export function pushCopiedToClipBoard() { - pushToastInfo('copiedToClipboard', window.i18n('copied')); + pushToastInfo('copiedToClipboard', getStrippedI18n('copied')); } export function pushRestartNeeded() { - pushToastInfo('restartNeeded', window.i18n('settingsRestartDescription')); + pushToastInfo('restartNeeded', getStrippedI18n('settingsRestartDescription')); } export function pushAlreadyMemberOpenGroup() { - pushToastInfo('publicChatExists', window.i18n('communityJoinedAlready')); + pushToastInfo('publicChatExists', getStrippedI18n('communityJoinedAlready')); } export function pushUserBanSuccess() { - pushToastSuccess('userBanned', window.i18n('banUserBanned')); + pushToastSuccess('userBanned', getStrippedI18n('banUserBanned')); } export function pushUserBanFailure() { - pushToastError('userBanFailed', window.i18n('banErrorFailed')); + pushToastError('userBanFailed', getStrippedI18n('banErrorFailed')); } export function pushUserUnbanSuccess() { - pushToastSuccess('userUnbanned', window.i18n('banUnbanUserUnbanned')); + pushToastSuccess('userUnbanned', getStrippedI18n('banUnbanUserUnbanned')); } export function pushUserUnbanFailure() { - pushToastError('userUnbanFailed', window.i18n('banUnbanErrorFailed')); + pushToastError('userUnbanFailed', getStrippedI18n('banUnbanErrorFailed')); } export function pushMessageDeleteForbidden() { pushToastError( 'messageDeletionForbidden', - window.i18n('deleteafterMessageDeletionStandardisationmessageDeletionForbidden') + getStrippedI18n('deleteafterMessageDeletionStandardisationmessageDeletionForbidden') ); } export function pushUnableToCall() { - pushToastError('unableToCall', window.i18n('callsCannotStart'), window.i18n('callsCannotStart')); + pushToastError('unableToCall', getStrippedI18n('callsCannotStart')); } -export function pushedMissedCall(conversationName: string) { - pushToastInfo('missedCall', window.i18n('callsMissedCallFrom', { name: conversationName })); +export function pushedMissedCall(userName: string) { + pushToastInfo('missedCall', getStrippedI18n('callsMissedCallFrom', { name: userName })); } const openPermissionsSettings = () => { @@ -133,8 +127,8 @@ export function pushedMissedCallCauseOfPermission(conversationName: string) { const id = 'missedCallPermission'; toast.info( , @@ -145,8 +139,8 @@ export function pushedMissedCallCauseOfPermission(conversationName: string) { export function pushVideoCallPermissionNeeded() { pushToastInfo( 'videoCallPermissionNeeded', - window.i18n('callsPermissionsRequired'), - window.i18n('callsPermissionsRequiredDescription'), + getStrippedI18n('callsPermissionsRequired'), + getStrippedI18n('callsPermissionsRequiredDescription'), openPermissionsSettings ); } @@ -154,83 +148,123 @@ export function pushVideoCallPermissionNeeded() { export function pushAudioPermissionNeeded() { pushToastInfo( 'audioPermissionNeeded', - window.i18n('permissionsMicrophoneAccessRequiredDesktop'), + getStrippedI18n('permissionsMicrophoneAccessRequiredDesktop'), undefined, openPermissionsSettings ); } export function pushOriginalNotFound() { - pushToastError('originalMessageNotFound', window.i18n('messageErrorOriginal')); + pushToastError('messageErrorOriginal', getStrippedI18n('messageErrorOriginal')); } export function pushTooManyMembers() { - pushToastError('tooManyMembers', window.i18n('groupAddMemberMaximum')); + pushToastError('groupAddMemberMaximum', getStrippedI18n('groupAddMemberMaximum')); } export function pushMessageRequestPending() { - pushToastInfo('messageRequestPending', window.i18n('messageRequestPending')); + pushToastInfo('messageRequestPending', getStrippedI18n('messageRequestPending')); } export function pushUnblockToSend() { - pushToastInfo('unblockToSend', window.i18n('blockBlockedDescription')); + pushToastInfo('unblockToSend', getStrippedI18n('blockBlockedDescription')); } export function pushYouLeftTheGroup() { - pushToastError('youLeftTheGroup', window.i18n('groupMemberYouLeft')); + pushToastError('youLeftTheGroup', getStrippedI18n('groupMemberYouLeft')); } -export function someDeletionsFailed() { - pushToastWarning('deletionError', 'Deletion error'); +export function someDeletionsFailed(count: number) { + pushToastWarning('deletionError', getStrippedI18n('deleteMessagesFailed', { count })); } export function pushDeleted() { - pushToastSuccess('deleted', window.i18n('deleteMessagesDeleted'), undefined); + pushToastSuccess('deleted', getStrippedI18n('deleteMessagesDeleted'), undefined); } export function pushCannotRemoveCreatorFromGroup() { - pushToastWarning('adminCannotBeRemoved', window.i18n('adminCannotBeRemoved')); + pushToastWarning('adminCannotBeRemoved', getStrippedI18n('adminCannotBeRemoved')); } export function pushFailedToAddAsModerator() { - pushToastWarning('adminPromotionFailed', window.i18n('adminPromotionFailed')); -} - -export function pushFailedToRemoveFromModerator(name: string) { - pushToastWarning( - 'adminRemoveFailed', - window.i18n('adminRemoveFailed', { - name, - }) - ); + pushToastWarning('adminPromotionFailed', getStrippedI18n('adminPromotionFailed')); +} + +export function pushFailedToRemoveFromModerator(names: Array) { + let localizedString: string = ''; + switch (names.length) { + case 0: + throw new Error('pushFailedToRemoveFromModerator invalid case error'); + case 1: + localizedString = getStrippedI18n('adminRemoveFailed', { + name: names[0], + }); + break; + case 2: + localizedString = getStrippedI18n('adminRemoveFailedOther', { + name: names[0], + other_name: names[1], + }); + break; + default: + localizedString = getStrippedI18n('adminRemoveFailedMultiple', { + name: names[0], + count: names.length - 1, + }); + break; + } + pushToastWarning('adminRemoveFailed', localizedString); } export function pushUserAddedToModerators(name: string) { - pushToastSuccess('adminPromotedToAdmin', window.i18n('adminPromotedToAdmin', { name })); -} + pushToastSuccess('adminPromotedToAdmin', getStrippedI18n('adminPromotedToAdmin', { name })); +} + +export function pushUserRemovedFromModerators(names: Array) { + let localizedString: string = ''; + switch (names.length) { + case 0: + throw new Error('pushUserRemovedFromModerators invalid case error'); + case 1: + localizedString = getStrippedI18n('adminRemovedUser', { + name: names[0], + }); + break; + case 2: + localizedString = getStrippedI18n('adminRemovedUserOther', { + name: names[0], + other_name: names[1], + }); + break; + default: + localizedString = getStrippedI18n('adminRemovedUserMultiple', { + name: names[0], + count: names.length - 1, + }); + break; + } -export function pushUserRemovedFromModerators(name: string) { - pushToastSuccess('adminRemovedUser', window.i18n('adminRemovedUser', { name })); + pushToastSuccess('adminRemovedUser', localizedString); } export function pushInvalidPubKey() { - pushToastSuccess('invalidPubKey', window.i18n('accountIdErrorInvalid')); + pushToastSuccess('accountIdErrorInvalid', getStrippedI18n('accountIdErrorInvalid')); } export function pushNoCameraFound() { - pushToastWarning('noCameraFound', window.i18n('cameraErrorNotFound')); + pushToastWarning('noCameraFound', getStrippedI18n('cameraErrorNotFound')); } export function pushNoAudioInputFound() { - pushToastWarning('noAudioInputFound', window.i18n('audioNoInput')); + pushToastWarning('noAudioInputFound', getStrippedI18n('audioNoInput')); } export function pushNoAudioOutputFound() { - pushToastWarning('noAudioOutputFound', window.i18n('audioNoOutput')); + pushToastWarning('noAudioOutputFound', getStrippedI18n('audioNoOutput')); } export function pushNoMediaUntilApproved() { - pushToastError('noMediaUntilApproved', window.i18n('messageRequestPendingDescription')); + pushToastError('noMediaUntilApproved', getStrippedI18n('messageRequestPendingDescription')); } export function pushRateLimitHitReactions() { diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 5473c6e55..403541d6b 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -26,7 +26,6 @@ import { hasValidIncomingRequestValues } from '../../models/conversation'; import { isOpenOrClosedGroup } from '../../models/conversationAttributes'; import { getConversationController } from '../../session/conversations'; import { UserUtils } from '../../session/utils'; -import { LocalizerType } from '../../types/Util'; import { BlockedNumberController } from '../../util'; import { Storage } from '../../util/storage'; import { getIntl } from './user'; @@ -220,23 +219,20 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector( } ); -function getConversationTitle( - conversation: ReduxConversationType, - testingi18n?: LocalizerType -): string { +function getConversationTitle(conversation: ReduxConversationType): string { if (conversation.displayNameInProfile) { return conversation.displayNameInProfile; } if (isOpenOrClosedGroup(conversation.type)) { - return (testingi18n || window.i18n)('unknown'); + return window.i18n('unknown'); } return conversation.id; } const collator = new Intl.Collator(); -export const _getConversationComparator = (testingi18n?: LocalizerType) => { +export const _getConversationComparator = () => { return (left: ReduxConversationType, right: ReduxConversationType): number => { // Pin is the first criteria to check const leftPriority = left.priority || 0; @@ -259,8 +255,8 @@ export const _getConversationComparator = (testingi18n?: LocalizerType) => { if (leftActiveAt && rightActiveAt && leftActiveAt !== rightActiveAt) { return rightActiveAt - leftActiveAt; } - const leftTitle = getConversationTitle(left, testingi18n).toLowerCase(); - const rightTitle = getConversationTitle(right, testingi18n).toLowerCase(); + const leftTitle = getConversationTitle(left).toLowerCase(); + const rightTitle = getConversationTitle(right).toLowerCase(); return collator.compare(leftTitle, rightTitle); }; diff --git a/ts/test/session/unit/selectors/conversations_test.ts b/ts/test/session/unit/selectors/conversations_test.ts index 43562a743..8664877b7 100644 --- a/ts/test/session/unit/selectors/conversations_test.ts +++ b/ts/test/session/unit/selectors/conversations_test.ts @@ -1,22 +1,22 @@ import { assert } from 'chai'; +import Sinon from 'sinon'; +import { CONVERSATION_PRIORITIES, ConversationTypeEnum } from '../../../../models/types'; import { ConversationLookupType } from '../../../../state/ducks/conversations'; import { _getConversationComparator, _getSortedConversations, } from '../../../../state/selectors/conversations'; -import { ConversationTypeEnum, CONVERSATION_PRIORITIES } from '../../../../models/types'; -import type { - GetMessageArgs, - LocalizerDictionary, - LocalizerToken, -} from '../../../../types/Localizer'; - -const i18n = ( - ...[token]: GetMessageArgs -) => token as any as R; +import { TestUtils } from '../../../test-utils'; describe('state/selectors/conversations', () => { + beforeEach(() => { + TestUtils.stubWindowLog(); + TestUtils.stubI18n(); + }); + afterEach(() => { + Sinon.restore(); + }); describe('#getSortedConversationsList', () => { it('sorts conversations based on timestamp then by intl-friendly title', () => { const data: ConversationLookupType = { @@ -138,7 +138,7 @@ describe('state/selectors/conversations', () => { priority: CONVERSATION_PRIORITIES.default, }, }; - const comparator = _getConversationComparator(i18n); + const comparator = _getConversationComparator(); const conversations = _getSortedConversations(data, comparator); assert.strictEqual(conversations[0].displayNameInProfile, 'First!'); @@ -277,7 +277,7 @@ describe('state/selectors/conversations', () => { isPublic: false, }, }; - const comparator = _getConversationComparator(i18n); + const comparator = _getConversationComparator(); const conversations = _getSortedConversations(data, comparator); assert.strictEqual(conversations[0].displayNameInProfile, 'Á'); diff --git a/ts/util/i18n.ts b/ts/util/i18n.ts index 82e6292c5..fd5880a7d 100644 --- a/ts/util/i18n.ts +++ b/ts/util/i18n.ts @@ -13,6 +13,9 @@ import { } from 'date-fns'; import timeLocales from 'date-fns/locale'; import { isUndefined } from 'lodash'; +import { GetNetworkTime } from '../session/apis/snode_api/getNetworkTime'; +import { DURATION_SECONDS, LOCALE_DEFAULTS } from '../session/constants'; +import { updateLocale } from '../state/ducks/dictionary'; import { DictionaryWithoutPluralStrings, GetMessageArgs, @@ -21,10 +24,6 @@ import { PluralKey, PluralString, } from '../types/Localizer'; -import { DURATION_SECONDS, LOCALE_DEFAULTS } from '../session/constants'; -import { updateLocale } from '../state/ducks/dictionary'; -import { GetNetworkTime } from '../session/apis/snode_api/getNetworkTime'; -import { Dictionary } from '../localization/locales'; export function loadDictionary(locale: Locale) { return import(`../../_locales/${locale}/messages.json`) as Promise; @@ -232,7 +231,7 @@ export const setupi18n = (locale: Locale, dictionary: LocalizerDictionary) => { return token as R; } - localizedString = pluralString.replaceAll('#', num) as R; + localizedString = pluralString.replaceAll('#', `${num}`) as R; } }