Merge pull request #2869 from Bilb/feature/ses-476/remove-profile-picture

Feature/ses 476/remove profile picture
pull/2885/head
Audric Ackermann 10 months ago committed by GitHub
commit 70b220400c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -509,5 +509,6 @@
"reactionPopupThree": "$name$, $name2$ & $name3$", "reactionPopupThree": "$name$, $name2$ & $name3$",
"reactionPopupMany": "$name$, $name2$, $name3$ &", "reactionPopupMany": "$name$, $name2$, $name3$ &",
"reactionListCountSingular": "And $otherSingular$ has reacted <span>$emoji$</span> to this message", "reactionListCountSingular": "And $otherSingular$ has reacted <span>$emoji$</span> to this message",
"reactionListCountPlural": "And $otherPlural$ have reacted <span>$emoji$</span> to this message" "reactionListCountPlural": "And $otherPlural$ have reacted <span>$emoji$</span> to this message",
"setDisplayPicture": "Set Display Picture"
} }

@ -6,6 +6,7 @@ from os import path, listdir
from glob import glob from glob import glob
import json import json
import sys import sys
from collections import OrderedDict
LOCALES_FOLDER = './_locales' LOCALES_FOLDER = './_locales'
@ -16,10 +17,10 @@ LOCALIZED_KEYS_FILE = './ts/types/LocalizerKeys.ts'
stringToWrite = "export type LocalizerKeys =\n | " stringToWrite = "export type LocalizerKeys =\n | "
with open(EN_FILE,'r') as jsonFile: with open(EN_FILE,'r') as jsonFile:
data = json.load(jsonFile) data = json.loads(jsonFile.read(), object_pairs_hook=OrderedDict)
keys = data.keys() keys = sorted(list(data.keys()))
stringToWrite += json.dumps(list(keys), sort_keys=True).replace(',', '\n |').replace('"', '\'')[1:-1] stringToWrite += json.dumps(keys, sort_keys=True).replace(',', '\n |').replace('"', '\'')[1:-1]
stringToWrite += ';\n' stringToWrite += ';\n'

@ -1,5 +1,6 @@
import autoBind from 'auto-bind'; import { useDispatch } from 'react-redux';
import React, { ChangeEvent, MouseEvent } from 'react'; // eslint-disable-next-line import/no-named-default
import { ChangeEvent, MouseEvent, default as React, ReactElement, useState } from 'react';
import { QRCode } from 'react-qr-svg'; import { QRCode } from 'react-qr-svg';
import styled from 'styled-components'; import styled from 'styled-components';
import { Avatar, AvatarSize } from '../avatar/Avatar'; import { Avatar, AvatarSize } from '../avatar/Avatar';
@ -7,15 +8,12 @@ import { Avatar, AvatarSize } from '../avatar/Avatar';
import { SyncUtils, ToastUtils, UserUtils } from '../../session/utils'; import { SyncUtils, ToastUtils, UserUtils } from '../../session/utils';
import { YourSessionIDPill, YourSessionIDSelectable } from '../basic/YourSessionIDPill'; import { YourSessionIDPill, YourSessionIDSelectable } from '../basic/YourSessionIDPill';
import { ConversationModel } from '../../models/conversation'; import { useOurAvatarPath, useOurConversationUsername } from '../../hooks/useParamSelector';
import { uploadOurAvatar } from '../../interactions/conversationInteractions';
import { ConversationTypeEnum } from '../../models/conversationAttributes'; import { ConversationTypeEnum } from '../../models/conversationAttributes';
import { MAX_USERNAME_BYTES } from '../../session/constants'; import { MAX_USERNAME_BYTES } from '../../session/constants';
import { getConversationController } from '../../session/conversations'; import { getConversationController } from '../../session/conversations';
import { sanitizeSessionUsername } from '../../session/utils/String'; import { sanitizeSessionUsername } from '../../session/utils/String';
import { editProfileModal } from '../../state/ducks/modalDialog'; import { editProfileModal, updateEditProfilePictureModel } from '../../state/ducks/modalDialog';
import { pickFileForAvatar } from '../../types/attachments/VisualAttachment';
import { saveQRCode } from '../../util/saveQRCode'; import { saveQRCode } from '../../util/saveQRCode';
import { setLastProfileUpdateTimestamp } from '../../util/storage'; import { setLastProfileUpdateTimestamp } from '../../util/storage';
import { SessionWrapperModal } from '../SessionWrapperModal'; import { SessionWrapperModal } from '../SessionWrapperModal';
@ -50,311 +48,255 @@ const QRView = ({ sessionID }: { sessionID: string }) => {
); );
}; };
interface State { const updateDisplayName = async (newName: string) => {
profileName: string; const ourNumber = UserUtils.getOurPubKeyStrFromCache();
updatedProfileName: string; const conversation = await getConversationController().getOrCreateAndWait(
oldAvatarPath: string; ourNumber,
newAvatarObjectUrl: string | null; ConversationTypeEnum.PRIVATE
mode: 'default' | 'edit' | 'qr'; );
loading: boolean; conversation.setSessionDisplayNameNoCommit(newName);
}
export class EditProfileDialog extends React.Component<object, State> {
private readonly convo: ConversationModel;
constructor(props: object) {
super(props);
autoBind(this);
this.convo = getConversationController().get(UserUtils.getOurPubKeyStrFromCache());
this.state = {
profileName: this.convo.getRealSessionUsername() || '',
updatedProfileName: this.convo.getRealSessionUsername() || '',
oldAvatarPath: this.convo.getAvatarPath() || '',
newAvatarObjectUrl: null,
mode: 'default',
loading: false,
};
}
public componentDidMount() {
window.addEventListener('keyup', this.onKeyUp);
}
public componentWillUnmount() { // might be good to not trigger a sync if the name did not change
window.removeEventListener('keyup', this.onKeyUp); await conversation.commit();
} await setLastProfileUpdateTimestamp(Date.now());
await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
};
public render() { type ProfileAvatarProps = {
const i18n = window.i18n; avatarPath: string | null;
newAvatarObjectUrl?: string | null;
profileName: string | undefined;
ourId: string;
};
const viewDefault = this.state.mode === 'default'; export const ProfileAvatar = (props: ProfileAvatarProps): ReactElement => {
const viewEdit = this.state.mode === 'edit'; const { newAvatarObjectUrl, avatarPath, profileName, ourId } = props;
const viewQR = this.state.mode === 'qr'; return (
<Avatar
forcedAvatarPath={newAvatarObjectUrl || avatarPath}
forcedName={profileName || ourId}
size={AvatarSize.XL}
pubkey={ourId}
/>
);
};
const sessionID = UserUtils.getOurPubKeyStrFromCache(); type ProfileHeaderProps = ProfileAvatarProps & {
onClick: () => void;
setMode: (mode: ProfileDialogModes) => void;
};
const backButton = const ProfileHeader = (props: ProfileHeaderProps): ReactElement => {
viewEdit || viewQR const { avatarPath, profileName, ourId, onClick, setMode } = props;
? [
{
iconType: 'chevron',
iconRotation: 90,
onClick: () => {
this.setState({ mode: 'default' });
},
},
]
: undefined;
return ( return (
<div className="edit-profile-dialog" data-testid="edit-profile-dialog"> <div className="avatar-center">
<SessionWrapperModal <div className="avatar-center-inner">
title={i18n('editProfileModalTitle')} <ProfileAvatar avatarPath={avatarPath} profileName={profileName} ourId={ourId} />
onClose={this.closeDialog} <div
headerIconButtons={backButton} className="image-upload-section"
showExitIcon={true} role="button"
onClick={onClick}
data-testid="image-upload-section"
/>
<div
className="qr-view-button"
onClick={() => {
setMode('qr');
}}
role="button"
> >
{viewQR && <QRView sessionID={sessionID} />} <SessionIconButton iconType="qr" iconSize="small" iconColor="var(--black-color)" />
{viewDefault && this.renderDefaultView()}
{viewEdit && this.renderEditView()}
<div className="session-id-section">
<YourSessionIDPill />
<YourSessionIDSelectable />
<SessionSpinner loading={this.state.loading} />
{viewDefault || viewQR ? (
<SessionButton
text={window.i18n('editMenuCopy')}
buttonType={SessionButtonType.Simple}
onClick={() => {
window.clipboard.writeText(sessionID);
ToastUtils.pushCopiedToClipBoard();
}}
dataTestId="copy-button-profile-update"
/>
) : (
!this.state.loading && (
<SessionButton
text={window.i18n('save')}
buttonType={SessionButtonType.Simple}
onClick={this.onClickOK}
disabled={this.state.loading}
dataTestId="save-button-profile-update"
/>
)
)}
</div>
</SessionWrapperModal>
</div>
);
}
private renderProfileHeader() {
return (
<>
<div className="avatar-center">
<div className="avatar-center-inner">
{this.renderAvatar()}
<div
className="image-upload-section"
role="button"
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onClick={this.fireInputEvent}
data-testid="image-upload-section"
/>
<div
className="qr-view-button"
onClick={() => {
this.setState(state => ({ ...state, mode: 'qr' }));
}}
role="button"
>
<SessionIconButton iconType="qr" iconSize="small" iconColor="var(--black-color)" />
</div>
</div>
</div> </div>
</> </div>
); </div>
} );
};
private async fireInputEvent() {
const scaledAvatarUrl = await pickFileForAvatar();
if (scaledAvatarUrl) { type ProfileDialogModes = 'default' | 'edit' | 'qr';
this.setState({ // tslint:disable-next-line: max-func-body-length
newAvatarObjectUrl: scaledAvatarUrl, export const EditProfileDialog = (): ReactElement => {
mode: 'edit', const dispatch = useDispatch();
});
}
}
private renderDefaultView() { const _profileName = useOurConversationUsername() || '';
const name = this.state.updatedProfileName || this.state.profileName; const [profileName, setProfileName] = useState(_profileName);
return ( const [updatedProfileName, setUpdateProfileName] = useState(profileName);
<> const avatarPath = useOurAvatarPath() || '';
{this.renderProfileHeader()} const ourId = UserUtils.getOurPubKeyStrFromCache();
<div className="profile-name-uneditable"> const [mode, setMode] = useState<ProfileDialogModes>('default');
<p data-testid="your-profile-name">{name}</p> const [loading, setLoading] = useState(false);
<SessionIconButton
iconType="pencil"
iconSize="medium"
onClick={() => {
this.setState({ mode: 'edit' });
}}
dataTestId="edit-profile-icon"
/>
</div>
</>
);
}
private renderEditView() { const closeDialog = () => {
const placeholderText = window.i18n('displayName'); window.removeEventListener('keyup', handleOnKeyUp);
window.inboxStore?.dispatch(editProfileModal(null));
};
const backButton =
mode === 'edit' || mode === 'qr'
? [
{
iconType: 'chevron',
iconRotation: 90,
onClick: () => {
setMode('default');
},
},
]
: undefined;
const onClickOK = async () => {
/**
* Tidy the profile name input text and save the new profile name and avatar
*/
try {
const newName = profileName ? profileName.trim() : '';
return ( if (newName.length === 0 || newName.length > MAX_USERNAME_BYTES) {
<> return;
{this.renderProfileHeader()} }
<div className="profile-name">
<input
type="text"
className="profile-name-input"
value={this.state.profileName}
placeholder={placeholderText}
onChange={this.onNameEdited}
maxLength={MAX_USERNAME_BYTES}
tabIndex={0}
required={true}
aria-required={true}
data-testid="profile-name-input"
/>
</div>
</>
);
}
private renderAvatar() { // this throw if the length in bytes is too long
const { oldAvatarPath, newAvatarObjectUrl, profileName } = this.state; const sanitizedName = sanitizeSessionUsername(newName);
const userName = profileName || this.convo.id; const trimName = sanitizedName.trim();
return ( setUpdateProfileName(trimName);
<Avatar setLoading(true);
forcedAvatarPath={newAvatarObjectUrl || oldAvatarPath}
forcedName={userName}
size={AvatarSize.XL}
pubkey={this.convo.id}
/>
);
}
private onNameEdited(event: ChangeEvent<HTMLInputElement>) { await updateDisplayName(newName);
const displayName = event.target.value; setMode('default');
try { setUpdateProfileName(profileName);
const newName = sanitizeSessionUsername(displayName); setLoading(false);
this.setState({
profileName: newName,
});
} catch (e) { } catch (e) {
this.setState({
profileName: displayName,
});
ToastUtils.pushToastError('nameTooLong', window.i18n('displayNameTooLong')); ToastUtils.pushToastError('nameTooLong', window.i18n('displayNameTooLong'));
} }
} };
private onKeyUp(event: any) { const handleOnKeyUp = (event: any) => {
switch (event.key) { switch (event.key) {
case 'Enter': case 'Enter':
if (this.state.mode === 'edit') { if (mode === 'edit') {
this.onClickOK(); void onClickOK();
} }
break; break;
case 'Esc': case 'Esc':
case 'Escape': case 'Escape':
this.closeDialog(); closeDialog();
break; break;
default: default:
} }
} };
const handleProfileHeaderClick = () => {
closeDialog();
dispatch(
updateEditProfilePictureModel({
avatarPath,
profileName,
ourId,
})
);
};
/** const onNameEdited = (event: ChangeEvent<HTMLInputElement>) => {
* Tidy the profile name input text and save the new profile name and avatar const displayName = event.target.value;
*/
private onClickOK() {
const { newAvatarObjectUrl, profileName } = this.state;
try { try {
const newName = profileName ? profileName.trim() : ''; const newName = sanitizeSessionUsername(displayName);
setProfileName(newName);
if (newName.length === 0 || newName.length > MAX_USERNAME_BYTES) {
return;
}
// this throw if the length in bytes is too long
const sanitizedName = sanitizeSessionUsername(newName);
const trimName = sanitizedName.trim();
this.setState(
{
profileName: trimName,
loading: true,
},
// eslint-disable-next-line @typescript-eslint/no-misused-promises
async () => {
await commitProfileEdits(newName, newAvatarObjectUrl);
this.setState({
loading: false,
mode: 'default',
updatedProfileName: this.state.profileName,
});
}
);
} catch (e) { } catch (e) {
setProfileName(displayName);
ToastUtils.pushToastError('nameTooLong', window.i18n('displayNameTooLong')); ToastUtils.pushToastError('nameTooLong', window.i18n('displayNameTooLong'));
} }
} };
private closeDialog() { return (
window.removeEventListener('keyup', this.onKeyUp); /* The <div> element has a child <input> element that allows keyboard interaction */
window.inboxStore?.dispatch(editProfileModal(null)); /* tslint:disable-next-line: react-a11y-event-has-role */
} <div className="edit-profile-dialog" data-testid="edit-profile-dialog" onKeyUp={handleOnKeyUp}>
} <SessionWrapperModal
title={window.i18n('editProfileModalTitle')}
onClose={closeDialog}
headerIconButtons={backButton}
showExitIcon={true}
>
{mode === 'qr' && <QRView sessionID={ourId} />}
{mode === 'default' && (
<>
<ProfileHeader
avatarPath={avatarPath}
profileName={profileName}
ourId={ourId}
onClick={handleProfileHeaderClick}
setMode={setMode}
/>
<div className="profile-name-uneditable">
<p data-testid="your-profile-name">{updatedProfileName || profileName}</p>
<SessionIconButton
iconType="pencil"
iconSize="medium"
onClick={() => {
setMode('edit');
}}
dataTestId="edit-profile-icon"
/>
</div>
</>
)}
{mode === 'edit' && (
<>
<ProfileHeader
avatarPath={avatarPath}
profileName={profileName}
ourId={ourId}
onClick={handleProfileHeaderClick}
setMode={setMode}
/>
<div className="profile-name">
<input
type="text"
className="profile-name-input"
value={profileName}
placeholder={window.i18n('displayName')}
onChange={onNameEdited}
maxLength={MAX_USERNAME_BYTES}
tabIndex={0}
required={true}
aria-required={true}
data-testid="profile-name-input"
/>
</div>
</>
)}
async function commitProfileEdits(newName: string, scaledAvatarUrl: string | null) { <div className="session-id-section">
const ourNumber = UserUtils.getOurPubKeyStrFromCache(); <YourSessionIDPill />
const conversation = await getConversationController().getOrCreateAndWait( <YourSessionIDSelectable />
ourNumber,
ConversationTypeEnum.PRIVATE
);
if (scaledAvatarUrl?.length) { <SessionSpinner loading={loading} />
try {
const blobContent = await (await fetch(scaledAvatarUrl)).blob();
if (!blobContent || !blobContent.size) {
throw new Error('Failed to fetch blob content from scaled avatar');
}
await uploadOurAvatar(await blobContent.arrayBuffer());
} catch (error) {
if (error.message && error.message.length) {
ToastUtils.pushToastError('edit-profile', error.message);
}
window.log.error(
'showEditProfileDialog Error ensuring that image is properly sized:',
error && error.stack ? error.stack : error
);
}
return;
}
// do not update the avatar if it did not change
conversation.setSessionDisplayNameNoCommit(newName);
// might be good to not trigger a sync if the name did not change {mode === 'default' || mode === 'qr' ? (
await conversation.commit(); <SessionButton
await setLastProfileUpdateTimestamp(Date.now()); text={window.i18n('editMenuCopy')}
await SyncUtils.forceSyncConfigurationNowIfNeeded(true); buttonType={SessionButtonType.Simple}
} onClick={() => {
window.clipboard.writeText(ourId);
ToastUtils.pushCopiedToClipBoard();
}}
dataTestId="copy-button-profile-update"
/>
) : (
!loading && (
<SessionButton
text={window.i18n('save')}
buttonType={SessionButtonType.Simple}
onClick={onClickOK}
disabled={loading}
dataTestId="save-button-profile-update"
/>
)
)}
</div>
</SessionWrapperModal>
</div>
);
};

@ -0,0 +1,164 @@
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { clearOurAvatar, uploadOurAvatar } from '../../interactions/conversationInteractions';
import { ToastUtils } from '../../session/utils';
import { editProfileModal, updateEditProfilePictureModel } from '../../state/ducks/modalDialog';
import { pickFileForAvatar } from '../../types/attachments/VisualAttachment';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SpacerLG } from '../basic/Text';
import { SessionIconButton } from '../icon';
import { ProfileAvatar } from './EditProfileDialog';
const StyledAvatarContainer = styled.div`
cursor: pointer;
`;
const UploadImageButton = () => {
return (
<div style={{ position: 'relative' }}>
<div style={{ borderRadius: '50%', overflow: 'hidden' }}>
<SessionIconButton
iconType="thumbnail"
iconSize="max"
iconPadding="16px"
backgroundColor="var(--chat-buttons-background-color)"
/>
</div>
<SessionIconButton
iconType="plusFat"
iconSize="medium"
iconColor="var(--modal-background-content-color)"
iconPadding="4.5px"
borderRadius="50%"
backgroundColor="var(--primary-color)"
style={{ position: 'absolute', bottom: 0, right: 0 }}
/>
</div>
);
};
const uploadProfileAvatar = async (scaledAvatarUrl: string | null) => {
if (scaledAvatarUrl?.length) {
try {
const blobContent = await (await fetch(scaledAvatarUrl)).blob();
if (!blobContent || !blobContent.size) {
throw new Error('Failed to fetch blob content from scaled avatar');
}
await uploadOurAvatar(await blobContent.arrayBuffer());
} catch (error) {
if (error.message && error.message.length) {
ToastUtils.pushToastError('edit-profile', error.message);
}
window.log.error(
'showEditProfileDialog Error ensuring that image is properly sized:',
error && error.stack ? error.stack : error
);
}
}
};
export type EditProfilePictureModalProps = {
avatarPath: string | null;
profileName: string | undefined;
ourId: string;
};
export const EditProfilePictureModal = (props: EditProfilePictureModalProps) => {
const dispatch = useDispatch();
const [newAvatarObjectUrl, setNewAvatarObjectUrl] = useState<string | null>(props.avatarPath);
const [loading, setLoading] = useState(false);
if (!props) {
return null;
}
const { avatarPath, profileName, ourId } = props;
const closeDialog = () => {
dispatch(updateEditProfilePictureModel(null));
dispatch(editProfileModal({}));
};
const handleAvatarClick = async () => {
const updatedAvatarObjectUrl = await pickFileForAvatar();
if (updatedAvatarObjectUrl) {
setNewAvatarObjectUrl(updatedAvatarObjectUrl);
}
};
const handleUpload = async () => {
setLoading(true);
if (newAvatarObjectUrl === avatarPath) {
window.log.debug('Avatar Object URL has not changed!');
return;
}
await uploadProfileAvatar(newAvatarObjectUrl);
setLoading(false);
dispatch(updateEditProfilePictureModel(null));
};
const handleRemove = async () => {
setLoading(true);
await clearOurAvatar();
setNewAvatarObjectUrl(null);
setLoading(false);
dispatch(updateEditProfilePictureModel(null));
};
return (
<SessionWrapperModal
title={window.i18n('setDisplayPicture')}
onClose={closeDialog}
showHeader={true}
showExitIcon={true}
>
<div
className="avatar-center"
role="button"
onClick={() => void handleAvatarClick()}
data-testid={'image-upload-click'}
>
<StyledAvatarContainer className="avatar-center-inner">
{newAvatarObjectUrl || avatarPath ? (
<ProfileAvatar
newAvatarObjectUrl={newAvatarObjectUrl}
avatarPath={avatarPath}
profileName={profileName}
ourId={ourId}
/>
) : (
<UploadImageButton />
)}
</StyledAvatarContainer>
</div>
{loading ? (
<SessionSpinner loading={loading} />
) : (
<>
<SpacerLG />
<div className="session-modal__button-group">
<SessionButton
text={window.i18n('save')}
buttonType={SessionButtonType.Simple}
onClick={handleUpload}
disabled={newAvatarObjectUrl === avatarPath}
dataTestId="save-button-profile-update"
/>
<SessionButton
text={window.i18n('remove')}
buttonColor={SessionButtonColor.Danger}
buttonType={SessionButtonType.Simple}
onClick={handleRemove}
disabled={!avatarPath}
/>
</div>
</>
)}
</SessionWrapperModal>
);
};

@ -8,6 +8,7 @@ import {
getConfirmModal, getConfirmModal,
getDeleteAccountModalState, getDeleteAccountModalState,
getEditProfileDialog, getEditProfileDialog,
getEditProfilePictureModalState,
getInviteContactModal, getInviteContactModal,
getOnionPathDialog, getOnionPathDialog,
getReactClearAllDialog, getReactClearAllDialog,
@ -36,6 +37,7 @@ import { SessionNicknameDialog } from './SessionNicknameDialog';
import { BanOrUnBanUserDialog } from './BanOrUnbanUserDialog'; import { BanOrUnBanUserDialog } from './BanOrUnbanUserDialog';
import { ReactListModal } from './ReactListModal'; import { ReactListModal } from './ReactListModal';
import { ReactClearAllModal } from './ReactClearAllModal'; import { ReactClearAllModal } from './ReactClearAllModal';
import { EditProfilePictureModal } from './EditProfilePictureModal';
export const ModalContainer = () => { export const ModalContainer = () => {
const confirmModalState = useSelector(getConfirmModal); const confirmModalState = useSelector(getConfirmModal);
@ -55,6 +57,7 @@ export const ModalContainer = () => {
const banOrUnbanUserModalState = useSelector(getBanOrUnbanUserModalState); const banOrUnbanUserModalState = useSelector(getBanOrUnbanUserModalState);
const reactListModalState = useSelector(getReactListDialog); const reactListModalState = useSelector(getReactListDialog);
const reactClearAllModalState = useSelector(getReactClearAllDialog); const reactClearAllModalState = useSelector(getReactClearAllDialog);
const editProfilePictureModalState = useSelector(getEditProfilePictureModalState);
return ( return (
<> <>
@ -79,6 +82,9 @@ export const ModalContainer = () => {
{confirmModalState && <SessionConfirm {...confirmModalState} />} {confirmModalState && <SessionConfirm {...confirmModalState} />}
{reactListModalState && <ReactListModal {...reactListModalState} />} {reactListModalState && <ReactListModal {...reactListModalState} />}
{reactClearAllModalState && <ReactClearAllModal {...reactClearAllModalState} />} {reactClearAllModalState && <ReactClearAllModal {...reactClearAllModalState} />}
{editProfilePictureModalState && (
<EditProfilePictureModal {...editProfilePictureModalState} />
)}
</> </>
); );
}; };

@ -66,6 +66,7 @@ export type SessionIconType =
| 'doubleCheckCircle' | 'doubleCheckCircle'
| 'gallery' | 'gallery'
| 'stop' | 'stop'
| 'thumbnail'
| 'timer00' | 'timer00'
| 'timer05' | 'timer05'
| 'timer10' | 'timer10'
@ -83,7 +84,7 @@ export type SessionIconType =
export type SessionIconSize = 'tiny' | 'small' | 'medium' | 'large' | 'huge' | 'huge2' | 'max'; export type SessionIconSize = 'tiny' | 'small' | 'medium' | 'large' | 'huge' | 'huge2' | 'max';
export const icons = { export const icons: Record<string, { path: string; viewBox: string; ratio: number }> = {
addUser: { addUser: {
path: path:
'M8.85,2.17c-1.73,0-3.12,1.4-3.12,3.12s1.4,3.12,3.12,3.12c1.73,0,3.13-1.4,3.13-3.12S10.58,2.17,8.85,2.17z M8.85,0.08c2.88,0,5.21,2.33,5.21,5.21s-2.33,5.21-5.21,5.21s-5.2-2.33-5.2-5.21C3.65,2.42,5.98,0.08,8.85,0.08z M20.83,5.29 c0.54,0,0.98,0.41,1.04,0.93l0.01,0.11v2.08h2.08c0.54,0,0.98,0.41,1.04,0.93v0.12c0,0.54-0.41,0.98-0.93,1.04l-0.11,0.01h-2.08 v2.08c0,0.58-0.47,1.04-1.04,1.04c-0.54,0-0.98-0.41-1.04-0.93l-0.01-0.11v-2.08h-2.08c-0.54,0-0.98-0.41-1.04-0.93l-0.01-0.11 c0-0.54,0.41-0.98,0.93-1.04l0.11-0.01h2.08V6.34C19.79,5.76,20.26,5.29,20.83,5.29z M12.5,12.58c2.8,0,5.09,2.21,5.2,4.99v0.22 v2.08c0,0.58-0.47,1.04-1.04,1.04c-0.54,0-0.98-0.41-1.04-0.93l-0.01-0.11v-2.08c0-1.67-1.3-3.03-2.95-3.12h-0.18H5.21 c-1.67,0-3.03,1.3-3.12,2.95v0.18v2.08c0,0.58-0.47,1.04-1.04,1.04c-0.54,0-0.98-0.41-1.04-0.93L0,19.88V17.8 c0-2.8,2.21-5.09,4.99-5.2h0.22h7.29V12.58z', 'M8.85,2.17c-1.73,0-3.12,1.4-3.12,3.12s1.4,3.12,3.12,3.12c1.73,0,3.13-1.4,3.13-3.12S10.58,2.17,8.85,2.17z M8.85,0.08c2.88,0,5.21,2.33,5.21,5.21s-2.33,5.21-5.21,5.21s-5.2-2.33-5.2-5.21C3.65,2.42,5.98,0.08,8.85,0.08z M20.83,5.29 c0.54,0,0.98,0.41,1.04,0.93l0.01,0.11v2.08h2.08c0.54,0,0.98,0.41,1.04,0.93v0.12c0,0.54-0.41,0.98-0.93,1.04l-0.11,0.01h-2.08 v2.08c0,0.58-0.47,1.04-1.04,1.04c-0.54,0-0.98-0.41-1.04-0.93l-0.01-0.11v-2.08h-2.08c-0.54,0-0.98-0.41-1.04-0.93l-0.01-0.11 c0-0.54,0.41-0.98,0.93-1.04l0.11-0.01h2.08V6.34C19.79,5.76,20.26,5.29,20.83,5.29z M12.5,12.58c2.8,0,5.09,2.21,5.2,4.99v0.22 v2.08c0,0.58-0.47,1.04-1.04,1.04c-0.54,0-0.98-0.41-1.04-0.93l-0.01-0.11v-2.08c0-1.67-1.3-3.03-2.95-3.12h-0.18H5.21 c-1.67,0-3.03,1.3-3.12,2.95v0.18v2.08c0,0.58-0.47,1.04-1.04,1.04c-0.54,0-0.98-0.41-1.04-0.93L0,19.88V17.8 c0-2.8,2.21-5.09,4.99-5.2h0.22h7.29V12.58z',
@ -475,6 +476,12 @@ export const icons = {
viewBox: '-1 -1 35 35', viewBox: '-1 -1 35 35',
ratio: 1, ratio: 1,
}, },
thumbnail: {
path:
'm34.915 23.899-8.321-6.087a.308.308 0 0 0-.385 0l-7.461 6.177a.334.334 0 0 1-.398 0l-3.75-3.057a.308.308 0 0 0-.372 0L4.07 28.15a.335.335 0 0 0-.129.257v2.337a.745.745 0 0 0 .732.732h29.638a.732.732 0 0 0 .731-.732v-6.6a.281.281 0 0 0-.128-.244Zm-23.82-5.15a2.89 2.89 0 1 0 0-5.778 2.89 2.89 0 0 0 0 5.778ZM40.36 4.624 8.193.874a3.197 3.197 0 0 0-3.57 2.838L4.469 5.1h2.568l.128-1.091a.63.63 0 0 1 .244-.424.642.642 0 0 1 .386-.141h.064L22.15 5.099h13.534a5.137 5.137 0 0 1 4.071 2.042h.308a.642.642 0 0 1 .565.706l-.09.732a5.14 5.14 0 0 1 .283 1.605v18.312L43.185 8.18a3.223 3.223 0 0 0-2.825-3.557Zm-4.675 30.678H3.3a3.21 3.21 0 0 1-3.21-3.21v-21.83a3.21 3.21 0 0 1 3.21-3.21h32.385a3.21 3.21 0 0 1 3.21 3.21v21.83a3.21 3.21 0 0 1-3.21 3.21ZM3.3 9.594a.642.642 0 0 0-.642.642v21.83a.642.642 0 0 0 .642.642h32.385a.642.642 0 0 0 .643-.642v-21.83a.642.642 0 0 0-.643-.642H3.3Z',
viewBox: '0 0 44 36',
ratio: 1,
},
timer00: { timer00: {
path: path:
'M11.428367,3.44328115 L10.5587469,3.94535651 C10.4906607,3.79477198 10.4145019,3.64614153 10.330127,3.5 C10.2457522,3.35385847 10.1551138,3.21358774 10.0587469,3.07933111 L10.928367,2.57725574 C11.0225793,2.71323387 11.1119641,2.85418158 11.1961524,3 C11.2803407,3.14581842 11.3577126,3.2937018 11.428367,3.44328115 Z M9.42274426,1.07163304 L8.92066889,1.94125309 C8.78641226,1.84488615 8.64614153,1.75424783 8.5,1.66987298 C8.35385847,1.58549813 8.20522802,1.50933927 8.05464349,1.44125309 L8.55671885,0.571633044 C8.7062982,0.642287382 8.85418158,0.719659271 9,0.803847577 C9.14581842,0.888035884 9.28676613,0.977420696 9.42274426,1.07163304 Z M11.9794631,6.5 L10.9753124,6.5 C10.9916403,6.33554688 11,6.1687497 11,6 C11,5.8312503 10.9916403,5.66445312 10.9753124,5.5 L11.9794631,5.5 C11.9930643,5.66486669 12,5.83162339 12,6 C12,6.16837661 11.9930643,6.33513331 11.9794631,6.5 Z M10.928367,9.42274426 L10.0587469,8.92066889 C10.1551138,8.78641226 10.2457522,8.64614153 10.330127,8.5 C10.4145019,8.35385847 10.4906607,8.20522802 10.5587469,8.05464349 L11.428367,8.55671885 C11.3577126,8.7062982 11.2803407,8.85418158 11.1961524,9 C11.1119641,9.14581842 11.0225793,9.28676613 10.928367,9.42274426 Z M8.55671885,11.428367 L8.05464349,10.5587469 C8.20522802,10.4906607 8.35385847,10.4145019 8.5,10.330127 C8.64614153,10.2457522 8.78641226,10.1551138 8.92066889,10.0587469 L9.42274426,10.928367 C9.28676613,11.0225793 9.14581842,11.1119641 9,11.1961524 C8.85418158,11.2803407 8.7062982,11.3577126 8.55671885,11.428367 Z M2.57725574,10.928367 L3.07933111,10.0587469 C3.21358774,10.1551138 3.35385847,10.2457522 3.5,10.330127 C3.64614153,10.4145019 3.79477198,10.4906607 3.94535651,10.5587469 L3.44328115,11.428367 C3.2937018,11.3577126 3.14581842,11.2803407 3,11.1961524 C2.85418158,11.1119641 2.71323387,11.0225793 2.57725574,10.928367 Z M5.5,11.9794631 L5.5,10.9753124 C5.66445312,10.9916403 5.8312503,11 6,11 C6.1687497,11 6.33554688,10.9916403 6.5,10.9753124 L6.5,11.9794631 C6.33513331,11.9930643 6.16837661,12 6,12 C5.83162339,12 5.66486669,11.9930643 5.5,11.9794631 Z M0.571633044,8.55671885 L1.44125309,8.05464349 C1.50933927,8.20522802 1.58549813,8.35385847 1.66987298,8.5 C1.75424783,8.64614153 1.84488615,8.78641226 1.94125309,8.92066889 L1.07163304,9.42274426 C0.977420696,9.28676613 0.888035884,9.14581842 0.803847577,9 C0.719659271,8.85418158 0.642287382,8.7062982 0.571633044,8.55671885 Z M0.0205368885,5.5 L1.02468762,5.5 C1.00835972,5.66445312 1,5.8312503 1,6 C1,6.1687497 1.00835972,6.33554688 1.02468762,6.5 L0.0205368885,6.5 C0.00693566443,6.33513331 -9.95062878e-13,6.16837661 -9.95093808e-13,6 C-9.95124738e-13,5.83162339 0.00693566443,5.66486669 0.0205368885,5.5 Z M1.07163304,2.57725574 L1.94125309,3.07933111 C1.84488615,3.21358774 1.75424783,3.35385847 1.66987298,3.5 C1.58549813,3.64614153 1.50933927,3.79477198 1.44125309,3.94535651 L0.571633044,3.44328115 C0.642287382,3.2937018 0.719659271,3.14581842 0.803847577,3 C0.888035884,2.85418158 0.977420696,2.71323387 1.07163304,2.57725574 Z M3.44328115,0.571633044 L3.94535651,1.44125309 C3.79477198,1.50933927 3.64614153,1.58549813 3.5,1.66987298 C3.35385847,1.75424783 3.21358774,1.84488615 3.07933111,1.94125309 L2.57725574,1.07163304 C2.71323387,0.977420696 2.85418158,0.888035884 3,0.803847577 C3.14581842,0.719659271 3.2937018,0.642287382 3.44328115,0.571633044 Z M6.5,0.0205368885 L6.5,7 L5.5,7 L5.5,0.0205368885 C5.66486669,0.00693566443 5.83162339,5.01e-14 6,5.01e-14 C6.16837661,5.01e-14 6.33513331,0.00693566443 6.5,0.0205368885 Z', 'M11.428367,3.44328115 L10.5587469,3.94535651 C10.4906607,3.79477198 10.4145019,3.64614153 10.330127,3.5 C10.2457522,3.35385847 10.1551138,3.21358774 10.0587469,3.07933111 L10.928367,2.57725574 C11.0225793,2.71323387 11.1119641,2.85418158 11.1961524,3 C11.2803407,3.14581842 11.3577126,3.2937018 11.428367,3.44328115 Z M9.42274426,1.07163304 L8.92066889,1.94125309 C8.78641226,1.84488615 8.64614153,1.75424783 8.5,1.66987298 C8.35385847,1.58549813 8.20522802,1.50933927 8.05464349,1.44125309 L8.55671885,0.571633044 C8.7062982,0.642287382 8.85418158,0.719659271 9,0.803847577 C9.14581842,0.888035884 9.28676613,0.977420696 9.42274426,1.07163304 Z M11.9794631,6.5 L10.9753124,6.5 C10.9916403,6.33554688 11,6.1687497 11,6 C11,5.8312503 10.9916403,5.66445312 10.9753124,5.5 L11.9794631,5.5 C11.9930643,5.66486669 12,5.83162339 12,6 C12,6.16837661 11.9930643,6.33513331 11.9794631,6.5 Z M10.928367,9.42274426 L10.0587469,8.92066889 C10.1551138,8.78641226 10.2457522,8.64614153 10.330127,8.5 C10.4145019,8.35385847 10.4906607,8.20522802 10.5587469,8.05464349 L11.428367,8.55671885 C11.3577126,8.7062982 11.2803407,8.85418158 11.1961524,9 C11.1119641,9.14581842 11.0225793,9.28676613 10.928367,9.42274426 Z M8.55671885,11.428367 L8.05464349,10.5587469 C8.20522802,10.4906607 8.35385847,10.4145019 8.5,10.330127 C8.64614153,10.2457522 8.78641226,10.1551138 8.92066889,10.0587469 L9.42274426,10.928367 C9.28676613,11.0225793 9.14581842,11.1119641 9,11.1961524 C8.85418158,11.2803407 8.7062982,11.3577126 8.55671885,11.428367 Z M2.57725574,10.928367 L3.07933111,10.0587469 C3.21358774,10.1551138 3.35385847,10.2457522 3.5,10.330127 C3.64614153,10.4145019 3.79477198,10.4906607 3.94535651,10.5587469 L3.44328115,11.428367 C3.2937018,11.3577126 3.14581842,11.2803407 3,11.1961524 C2.85418158,11.1119641 2.71323387,11.0225793 2.57725574,10.928367 Z M5.5,11.9794631 L5.5,10.9753124 C5.66445312,10.9916403 5.8312503,11 6,11 C6.1687497,11 6.33554688,10.9916403 6.5,10.9753124 L6.5,11.9794631 C6.33513331,11.9930643 6.16837661,12 6,12 C5.83162339,12 5.66486669,11.9930643 5.5,11.9794631 Z M0.571633044,8.55671885 L1.44125309,8.05464349 C1.50933927,8.20522802 1.58549813,8.35385847 1.66987298,8.5 C1.75424783,8.64614153 1.84488615,8.78641226 1.94125309,8.92066889 L1.07163304,9.42274426 C0.977420696,9.28676613 0.888035884,9.14581842 0.803847577,9 C0.719659271,8.85418158 0.642287382,8.7062982 0.571633044,8.55671885 Z M0.0205368885,5.5 L1.02468762,5.5 C1.00835972,5.66445312 1,5.8312503 1,6 C1,6.1687497 1.00835972,6.33554688 1.02468762,6.5 L0.0205368885,6.5 C0.00693566443,6.33513331 -9.95062878e-13,6.16837661 -9.95093808e-13,6 C-9.95124738e-13,5.83162339 0.00693566443,5.66486669 0.0205368885,5.5 Z M1.07163304,2.57725574 L1.94125309,3.07933111 C1.84488615,3.21358774 1.75424783,3.35385847 1.66987298,3.5 C1.58549813,3.64614153 1.50933927,3.79477198 1.44125309,3.94535651 L0.571633044,3.44328115 C0.642287382,3.2937018 0.719659271,3.14581842 0.803847577,3 C0.888035884,2.85418158 0.977420696,2.71323387 1.07163304,2.57725574 Z M3.44328115,0.571633044 L3.94535651,1.44125309 C3.79477198,1.50933927 3.64614153,1.58549813 3.5,1.66987298 C3.35385847,1.75424783 3.21358774,1.84488615 3.07933111,1.94125309 L2.57725574,1.07163304 C2.71323387,0.977420696 2.85418158,0.888035884 3,0.803847577 C3.14581842,0.719659271 3.2937018,0.642287382 3.44328115,0.571633044 Z M6.5,0.0205368885 L6.5,7 L5.5,7 L5.5,0.0205368885 C5.66486669,0.00693566443 5.83162339,5.01e-14 6,5.01e-14 C6.16837661,5.01e-14 6.33513331,0.00693566443 6.5,0.0205368885 Z',
@ -536,10 +543,8 @@ export const icons = {
ratio: 1, ratio: 1,
}, },
timer50: { timer50: {
path: [ path:
'M8.49999998,1.66987313 L8.99999998,0.80384773 C10.3298113,1.57161469 11.3667294,2.84668755 11.7955548,4.4470858 C12.6532057,7.64788242 10.7537107,10.9379042 7.5529141,11.795555 C4.35211748,12.6532059 1.06209574,10.753711 0.204444873,7.55291434 C-0.27253249,5.77281059 0.103264647,3.96510985 1.08141192,2.56355986 L1.94944135,3.0683506 L1.94144625,3.07944279 L6.25000012,5.56698754 L5.75000012,6.43301294 L1.44144624,3.9454682 C0.98322086,4.96059039 0.85962347,6.13437085 1.1703707,7.29409529 C1.88507976,9.96142581 4.62676454,11.5443383 7.29409506,10.8296292 C9.96142557,10.1149201 11.544338,7.37323536 10.829629,4.70590484 C10.4722744,3.37223964 9.60817605,2.30967894 8.49999998,1.66987313 Z', 'M8.49999998,1.66987313 L8.99999998,0.80384773 C10.3298113,1.57161469 11.3667294,2.84668755 11.7955548,4.4470858 C12.6532057,7.64788242 10.7537107,10.9379042 7.5529141,11.795555 C4.35211748,12.6532059 1.06209574,10.753711 0.204444873,7.55291434 C-0.27253249,5.77281059 0.103264647,3.96510985 1.08141192,2.56355986 L1.94944135,3.0683506 L1.94144625,3.07944279 L6.25000012,5.56698754 L5.75000012,6.43301294 L1.44144624,3.9454682 C0.98322086,4.96059039 0.85962347,6.13437085 1.1703707,7.29409529 C1.88507976,9.96142581 4.62676454,11.5443383 7.29409506,10.8296292 C9.96142557,10.1149201 11.544338,7.37323536 10.829629,4.70590484 C10.4722744,3.37223964 9.60817605,2.30967894 8.49999998,1.66987313 Z M6.00250506,1.00000061 L6,1 C5.8312503,1 5.66445312,1.00835972 5.5,1.02468762 L5.5,0.0205368885 C5.66486669,0.00693566443 5.83162339,0 6,0 L6.00250686,5.12480482e-07 C9.31506271,0.0013544265 12,2.68712686 12,6 L11,6 C11,3.23941132 8.76277746,1.00135396 6.00250506,1.00000061 Z M3.44328115,0.571633044 L3.94535651,1.44125309 C3.79477198,1.50933927 3.64614153,1.58549813 3.5,1.66987298 C3.35385847,1.75424783 3.21358774,1.84488615 3.07933111,1.94125309 L2.57725574,1.07163304 C2.71323387,0.977420696 2.85418158,0.888035884 3,0.803847577 C3.14581842,0.719659271 3.2937018,0.642287382 3.44328115,0.571633044 Z',
'M6.00250506,1.00000061 L6,1 C5.8312503,1 5.66445312,1.00835972 5.5,1.02468762 L5.5,0.0205368885 C5.66486669,0.00693566443 5.83162339,0 6,0 L6.00250686,5.12480482e-07 C9.31506271,0.0013544265 12,2.68712686 12,6 L11,6 C11,3.23941132 8.76277746,1.00135396 6.00250506,1.00000061 Z M3.44328115,0.571633044 L3.94535651,1.44125309 C3.79477198,1.50933927 3.64614153,1.58549813 3.5,1.66987298 C3.35385847,1.75424783 3.21358774,1.84488615 3.07933111,1.94125309 L2.57725574,1.07163304 C2.71323387,0.977420696 2.85418158,0.888035884 3,0.803847577 C3.14581842,0.719659271 3.2937018,0.642287382 3.44328115,0.571633044 Z',
],
viewBox: '0 0 12 12', viewBox: '0 0 12 12',
ratio: 1, ratio: 1,
}, },

@ -1,3 +1,4 @@
import { isNil } from 'lodash';
import { import {
ConversationNotificationSettingType, ConversationNotificationSettingType,
ConversationTypeEnum, ConversationTypeEnum,
@ -487,6 +488,37 @@ export async function uploadOurAvatar(newAvatarDecrypted?: ArrayBuffer) {
}; };
} }
/**
* This function can be used for clearing our avatar.
*/
export async function clearOurAvatar(commit: boolean = true) {
const ourConvo = getConversationController().get(UserUtils.getOurPubKeyStrFromCache());
if (!ourConvo) {
window.log.warn('ourConvo not found... This is not a valid case');
return;
}
// return early if no change are needed at all
if (
isNil(ourConvo.get('avatarPointer')) &&
isNil(ourConvo.get('avatarInProfile')) &&
isNil(ourConvo.get('profileKey'))
) {
return;
}
ourConvo.set('avatarPointer', undefined);
ourConvo.set('avatarInProfile', undefined);
ourConvo.set('profileKey', undefined);
await setLastProfileUpdateTimestamp(Date.now());
if (commit) {
await ourConvo.commit();
await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
}
}
export async function replyToMessage(messageId: string) { export async function replyToMessage(messageId: string) {
const quotedMessageModel = await Data.getMessageById(messageId); const quotedMessageModel = await Data.getMessageById(messageId);
if (!quotedMessageModel) { if (!quotedMessageModel) {

@ -1,6 +1,7 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { SessionConfirmDialogProps } from '../../components/dialog/SessionConfirm'; import { SessionConfirmDialogProps } from '../../components/dialog/SessionConfirm';
import { PasswordAction } from '../../components/dialog/SessionPasswordDialog'; import { PasswordAction } from '../../components/dialog/SessionPasswordDialog';
import { EditProfilePictureModalProps } from '../../components/dialog/EditProfilePictureModal';
import { Noop } from '../../types/Util'; import { Noop } from '../../types/Util';
export type BanType = 'ban' | 'unban'; export type BanType = 'ban' | 'unban';
@ -36,6 +37,8 @@ export type ReactModalsState = {
messageId: string; messageId: string;
} | null; } | null;
export type EditProfilePictureModalState = EditProfilePictureModalProps | null;
export type ModalState = { export type ModalState = {
confirmModal: ConfirmModalState; confirmModal: ConfirmModalState;
inviteContactModal: InviteContactModalState; inviteContactModal: InviteContactModalState;
@ -54,6 +57,7 @@ export type ModalState = {
deleteAccountModal: DeleteAccountModalState; deleteAccountModal: DeleteAccountModalState;
reactListModalState: ReactModalsState; reactListModalState: ReactModalsState;
reactClearAllModalState: ReactModalsState; reactClearAllModalState: ReactModalsState;
editProfilePictureModalState: EditProfilePictureModalState;
}; };
export const initialModalState: ModalState = { export const initialModalState: ModalState = {
@ -74,6 +78,7 @@ export const initialModalState: ModalState = {
deleteAccountModal: null, deleteAccountModal: null,
reactListModalState: null, reactListModalState: null,
reactClearAllModalState: null, reactClearAllModalState: null,
editProfilePictureModalState: null,
}; };
const ModalSlice = createSlice({ const ModalSlice = createSlice({
@ -131,6 +136,9 @@ const ModalSlice = createSlice({
updateReactClearAllModal(state, action: PayloadAction<ReactModalsState>) { updateReactClearAllModal(state, action: PayloadAction<ReactModalsState>) {
return { ...state, reactClearAllModalState: action.payload }; return { ...state, reactClearAllModalState: action.payload };
}, },
updateEditProfilePictureModel(state, action: PayloadAction<EditProfilePictureModalState>) {
return { ...state, editProfilePictureModalState: action.payload };
},
}, },
}); });
@ -153,5 +161,6 @@ export const {
updateBanOrUnbanUserModal, updateBanOrUnbanUserModal,
updateReactListModal, updateReactListModal,
updateReactClearAllModal, updateReactClearAllModal,
updateEditProfilePictureModel,
} = actions; } = actions;
export const modalReducer = reducer; export const modalReducer = reducer;

@ -9,6 +9,7 @@ import {
ConfirmModalState, ConfirmModalState,
DeleteAccountModalState, DeleteAccountModalState,
EditProfileModalState, EditProfileModalState,
EditProfilePictureModalState,
InviteContactModalState, InviteContactModalState,
ModalState, ModalState,
OnionPathModalState, OnionPathModalState,
@ -109,3 +110,8 @@ export const getReactClearAllDialog = createSelector(
getModal, getModal,
(state: ModalState): ReactModalsState => state.reactClearAllModalState (state: ModalState): ReactModalsState => state.reactClearAllModalState
); );
export const getEditProfilePictureModalState = createSelector(
getModal,
(state: ModalState): EditProfilePictureModalState => state.editProfilePictureModalState
);

@ -1,512 +1,513 @@
export type LocalizerKeys = export type LocalizerKeys =
| 'copyErrorAndQuit' | 'ByUsingThisService...'
| 'unknown' | 'about'
| 'databaseError' | 'accept'
| 'mainMenuFile' | 'activeMembers'
| 'mainMenuEdit' | 'add'
| 'mainMenuView' | 'addACaption'
| 'mainMenuWindow' | 'addAsModerator'
| 'mainMenuHelp' | 'addModerators'
| 'addingContacts'
| 'allUsersAreRandomly...'
| 'anonymous'
| 'answeredACall'
| 'appMenuHide' | 'appMenuHide'
| 'appMenuHideOthers' | 'appMenuHideOthers'
| 'appMenuUnhide'
| 'appMenuQuit' | 'appMenuQuit'
| 'editMenuUndo' | 'appMenuUnhide'
| 'editMenuRedo' | 'appearanceSettingsTitle'
| 'editMenuCut' | 'areYouSureClearDevice'
| 'editMenuCopy' | 'areYouSureDeleteDeviceOnly'
| 'editMenuPaste' | 'areYouSureDeleteEntireAccount'
| 'editMenuDeleteContact'
| 'editMenuDeleteGroup'
| 'editMenuSelectAll'
| 'windowMenuClose'
| 'windowMenuMinimize'
| 'windowMenuZoom'
| 'viewMenuResetZoom'
| 'viewMenuZoomIn'
| 'viewMenuZoomOut'
| 'viewMenuToggleFullScreen'
| 'viewMenuToggleDevTools'
| 'contextMenuNoSuggestions'
| 'openGroupInvitation'
| 'joinOpenGroupAfterInvitationConfirmationTitle'
| 'joinOpenGroupAfterInvitationConfirmationDesc'
| 'couldntFindServerMatching'
| 'enterSessionIDOrONSName'
| 'startNewConversationBy...'
| 'loading'
| 'done'
| 'youLeftTheGroup'
| 'youGotKickedFromGroup'
| 'unreadMessages'
| 'debugLogExplanation'
| 'reportIssue'
| 'markAllAsRead'
| 'incomingError'
| 'media'
| 'mediaEmptyState'
| 'document'
| 'documents'
| 'documentsEmptyState'
| 'today'
| 'yesterday'
| 'thisWeek'
| 'thisMonth'
| 'voiceMessage'
| 'stagedPreviewThumbnail'
| 'previewThumbnail'
| 'stagedImageAttachment'
| 'oneNonImageAtATimeToast'
| 'cannotMixImageAndNonImageAttachments'
| 'maximumAttachments'
| 'fileSizeWarning'
| 'unableToLoadAttachment'
| 'offline'
| 'debugLog'
| 'showDebugLog'
| 'shareBugDetails'
| 'goToReleaseNotes'
| 'goToSupportPage'
| 'about'
| 'show'
| 'sessionMessenger'
| 'noSearchResults'
| 'conversationsHeader'
| 'contactsHeader'
| 'messagesHeader'
| 'searchMessagesHeader'
| 'settingsHeader'
| 'typingAlt'
| 'contactAvatarAlt'
| 'downloadAttachment'
| 'replyToMessage'
| 'replyingToMessage'
| 'originalMessageNotFound'
| 'you'
| 'audioPermissionNeededTitle'
| 'audioPermissionNeeded'
| 'image'
| 'audio' | 'audio'
| 'video' | 'audioMessageAutoplayDescription'
| 'photo' | 'audioMessageAutoplayTitle'
| 'audioNotificationsSettingsTitle'
| 'audioPermissionNeeded'
| 'audioPermissionNeededTitle'
| 'autoUpdateDownloadButtonLabel'
| 'autoUpdateDownloadInstructions'
| 'autoUpdateDownloadedMessage'
| 'autoUpdateLaterButtonLabel'
| 'autoUpdateNewVersionInstructions'
| 'autoUpdateNewVersionMessage'
| 'autoUpdateNewVersionTitle'
| 'autoUpdateRestartButtonLabel'
| 'autoUpdateSettingDescription'
| 'autoUpdateSettingTitle'
| 'banUser'
| 'banUserAndDeleteAll'
| 'beginYourSession'
| 'blindedMsgReqsSettingDesc'
| 'blindedMsgReqsSettingTitle'
| 'block'
| 'blocked'
| 'blockedSettingsTitle'
| 'callMediaPermissionsDescription'
| 'callMediaPermissionsDialogContent'
| 'callMediaPermissionsDialogTitle'
| 'callMediaPermissionsTitle'
| 'callMissed'
| 'callMissedCausePermission'
| 'callMissedNotApproved'
| 'callMissedTitle'
| 'cameraPermissionNeeded'
| 'cameraPermissionNeededTitle'
| 'cancel'
| 'cannotMixImageAndNonImageAttachments'
| 'cannotRemoveCreatorFromGroup'
| 'cannotRemoveCreatorFromGroupDesc'
| 'cannotUpdate' | 'cannotUpdate'
| 'cannotUpdateDetail' | 'cannotUpdateDetail'
| 'ok' | 'changeAccountPasswordDescription'
| 'cancel' | 'changeAccountPasswordTitle'
| 'changeNickname'
| 'changeNicknameMessage'
| 'changePassword'
| 'changePasswordInvalid'
| 'changePasswordTitle'
| 'changePasswordToastDescription'
| 'chooseAnAction'
| 'classicDarkThemeTitle'
| 'classicLightThemeTitle'
| 'clear'
| 'clearAll'
| 'clearAllConfirmationBody'
| 'clearAllConfirmationTitle'
| 'clearAllData'
| 'clearAllReactions'
| 'clearDataSettingsTitle'
| 'clearDevice'
| 'clearNickname'
| 'clickToTrustContact'
| 'close' | 'close'
| 'closedGroupInviteFailMessage'
| 'closedGroupInviteFailMessagePlural'
| 'closedGroupInviteFailTitle'
| 'closedGroupInviteFailTitlePlural'
| 'closedGroupInviteOkText'
| 'closedGroupInviteSuccessMessage'
| 'closedGroupInviteSuccessTitle'
| 'closedGroupInviteSuccessTitlePlural'
| 'closedGroupMaxSize'
| 'confirmNewPassword'
| 'confirmPassword'
| 'connectToServerFail'
| 'connectToServerSuccess'
| 'connectingToServer'
| 'contactAvatarAlt'
| 'contactsHeader'
| 'contextMenuNoSuggestions'
| 'continue' | 'continue'
| 'error' | 'continueYourSession'
| 'conversationsHeader'
| 'conversationsSettingsTitle'
| 'copiedToClipboard'
| 'copyErrorAndQuit'
| 'copyMessage'
| 'copyOpenGroupURL'
| 'copySessionID'
| 'couldntFindServerMatching'
| 'create'
| 'createAccount'
| 'createClosedGroupNamePrompt'
| 'createClosedGroupPlaceholder'
| 'createConversationNewContact'
| 'createConversationNewGroup'
| 'createGroup'
| 'createPassword'
| 'createSessionID'
| 'databaseError'
| 'debugLog'
| 'debugLogExplanation'
| 'decline'
| 'declineRequestMessage'
| 'delete' | 'delete'
| 'messageDeletionForbidden' | 'deleteAccountFromLogin'
| 'deleteJustForMe' | 'deleteAccountWarning'
| 'deleteContactConfirmation'
| 'deleteConversation'
| 'deleteConversationConfirmation'
| 'deleteForEveryone' | 'deleteForEveryone'
| 'deleteMessagesQuestion' | 'deleteJustForMe'
| 'deleteMessageQuestion' | 'deleteMessageQuestion'
| 'deleteMessages' | 'deleteMessages'
| 'deleteConversation' | 'deleteMessagesQuestion'
| 'deleted' | 'deleted'
| 'messageDeletedPlaceholder' | 'destination'
| 'from' | 'device'
| 'to' | 'deviceOnly'
| 'sent' | 'dialogClearAllDataDeletionFailedDesc'
| 'received' | 'dialogClearAllDataDeletionFailedMultiple'
| 'sendMessage' | 'dialogClearAllDataDeletionFailedTitle'
| 'groupMembers' | 'dialogClearAllDataDeletionFailedTitleQuestion'
| 'moreInformation' | 'dialogClearAllDataDeletionQuestion'
| 'resend' | 'disabledDisappearingMessages'
| 'deleteConversationConfirmation'
| 'clear'
| 'clearAllData'
| 'deleteAccountWarning'
| 'deleteAccountFromLogin'
| 'deleteContactConfirmation'
| 'quoteThumbnailAlt'
| 'imageAttachmentAlt'
| 'videoAttachmentAlt'
| 'lightboxImageAlt'
| 'imageCaptionIconAlt'
| 'addACaption'
| 'copySessionID'
| 'copyOpenGroupURL'
| 'save'
| 'saveLogToDesktop'
| 'saved'
| 'tookAScreenshot'
| 'savedTheFile'
| 'linkPreviewsTitle'
| 'linkPreviewDescription'
| 'linkPreviewsConfirmMessage'
| 'mediaPermissionsTitle'
| 'mediaPermissionsDescription'
| 'spellCheckTitle'
| 'spellCheckDescription'
| 'spellCheckDirty'
| 'readReceiptSettingDescription'
| 'readReceiptSettingTitle'
| 'typingIndicatorsSettingDescription'
| 'typingIndicatorsSettingTitle'
| 'zoomFactorSettingTitle'
| 'themesSettingTitle'
| 'primaryColor'
| 'primaryColorGreen'
| 'primaryColorBlue'
| 'primaryColorYellow'
| 'primaryColorPink'
| 'primaryColorPurple'
| 'primaryColorOrange'
| 'primaryColorRed'
| 'classicDarkThemeTitle'
| 'classicLightThemeTitle'
| 'oceanDarkThemeTitle'
| 'oceanLightThemeTitle'
| 'pruneSettingTitle'
| 'pruneSettingDescription'
| 'enable'
| 'keepDisabled'
| 'notificationSettingsDialog'
| 'nameAndMessage'
| 'noNameOrMessage'
| 'nameOnly'
| 'newMessage'
| 'createConversationNewContact'
| 'createConversationNewGroup'
| 'joinACommunity'
| 'chooseAnAction'
| 'newMessages'
| 'notificationMostRecentFrom'
| 'notificationFrom'
| 'notificationMostRecent'
| 'sendFailed'
| 'mediaMessage'
| 'messageBodyMissing'
| 'messageBody'
| 'unblockToSend'
| 'youChangedTheTimer'
| 'timerSetOnSync'
| 'theyChangedTheTimer'
| 'timerOption_0_seconds'
| 'timerOption_5_seconds'
| 'timerOption_10_seconds'
| 'timerOption_30_seconds'
| 'timerOption_1_minute'
| 'timerOption_5_minutes'
| 'timerOption_30_minutes'
| 'timerOption_1_hour'
| 'timerOption_6_hours'
| 'timerOption_12_hours'
| 'timerOption_1_day'
| 'timerOption_1_week'
| 'timerOption_2_weeks'
| 'disappearingMessages' | 'disappearingMessages'
| 'changeNickname'
| 'clearNickname'
| 'nicknamePlaceholder'
| 'changeNicknameMessage'
| 'timerOption_0_seconds_abbreviated'
| 'timerOption_5_seconds_abbreviated'
| 'timerOption_10_seconds_abbreviated'
| 'timerOption_30_seconds_abbreviated'
| 'timerOption_1_minute_abbreviated'
| 'timerOption_5_minutes_abbreviated'
| 'timerOption_30_minutes_abbreviated'
| 'timerOption_1_hour_abbreviated'
| 'timerOption_6_hours_abbreviated'
| 'timerOption_12_hours_abbreviated'
| 'timerOption_1_day_abbreviated'
| 'timerOption_1_week_abbreviated'
| 'timerOption_2_weeks_abbreviated'
| 'disappearingMessagesDisabled' | 'disappearingMessagesDisabled'
| 'disabledDisappearingMessages' | 'displayName'
| 'youDisabledDisappearingMessages' | 'displayNameEmpty'
| 'timerSetTo' | 'displayNameTooLong'
| 'noteToSelf' | 'document'
| 'hideMenuBarTitle' | 'documents'
| 'documentsEmptyState'
| 'done'
| 'downloadAttachment'
| 'editGroup'
| 'editGroupName'
| 'editMenuCopy'
| 'editMenuCut'
| 'editMenuDeleteContact'
| 'editMenuDeleteGroup'
| 'editMenuPaste'
| 'editMenuRedo'
| 'editMenuSelectAll'
| 'editMenuUndo'
| 'editProfileModalTitle'
| 'emptyGroupNameError'
| 'enable'
| 'endCall'
| 'enterAnOpenGroupURL'
| 'enterDisplayName'
| 'enterNewPassword'
| 'enterPassword'
| 'enterRecoveryPhrase'
| 'enterSessionID'
| 'enterSessionIDOfRecipient'
| 'enterSessionIDOrONSName'
| 'entireAccount'
| 'error'
| 'establishingConnection'
| 'expandedReactionsText'
| 'failedResolveOns'
| 'failedToAddAsModerator'
| 'failedToRemoveFromModerator'
| 'faq'
| 'fileSizeWarning'
| 'from'
| 'getStarted'
| 'goToReleaseNotes'
| 'goToSupportPage'
| 'groupMembers'
| 'groupNamePlaceholder'
| 'helpSettingsTitle'
| 'helpUsTranslateSession'
| 'hideBanner'
| 'hideMenuBarDescription' | 'hideMenuBarDescription'
| 'startConversation' | 'hideMenuBarTitle'
| 'hideRequestBanner'
| 'hideRequestBannerDescription'
| 'iAmSure'
| 'image'
| 'imageAttachmentAlt'
| 'imageCaptionIconAlt'
| 'incomingCallFrom'
| 'incomingError'
| 'invalidGroupNameTooLong'
| 'invalidGroupNameTooShort'
| 'invalidNumberError' | 'invalidNumberError'
| 'failedResolveOns' | 'invalidOldPassword'
| 'autoUpdateSettingTitle' | 'invalidOpenGroupUrl'
| 'autoUpdateSettingDescription' | 'invalidPassword'
| 'autoUpdateNewVersionTitle' | 'invalidPubkeyFormat'
| 'autoUpdateNewVersionMessage' | 'invalidSessionId'
| 'autoUpdateNewVersionInstructions' | 'inviteContacts'
| 'autoUpdateRestartButtonLabel' | 'join'
| 'autoUpdateLaterButtonLabel' | 'joinACommunity'
| 'autoUpdateDownloadButtonLabel' | 'joinOpenGroup'
| 'autoUpdateDownloadedMessage' | 'joinOpenGroupAfterInvitationConfirmationDesc'
| 'autoUpdateDownloadInstructions' | 'joinOpenGroupAfterInvitationConfirmationTitle'
| 'leftTheGroup'
| 'multipleLeftTheGroup'
| 'updatedTheGroup'
| 'titleIsNow'
| 'joinedTheGroup' | 'joinedTheGroup'
| 'multipleJoinedTheGroup' | 'keepDisabled'
| 'kickedFromTheGroup' | 'kickedFromTheGroup'
| 'multipleKickedFromTheGroup' | 'learnMore'
| 'block'
| 'unblock'
| 'unblocked'
| 'blocked'
| 'blockedSettingsTitle'
| 'conversationsSettingsTitle'
| 'unbanUser'
| 'userUnbanned'
| 'userUnbanFailed'
| 'banUser'
| 'banUserAndDeleteAll'
| 'userBanned'
| 'userBanFailed'
| 'leaveGroup'
| 'leaveAndRemoveForEveryone' | 'leaveAndRemoveForEveryone'
| 'leaveGroup'
| 'leaveGroupConfirmation' | 'leaveGroupConfirmation'
| 'leaveGroupConfirmationAdmin' | 'leaveGroupConfirmationAdmin'
| 'cannotRemoveCreatorFromGroup' | 'leftTheGroup'
| 'cannotRemoveCreatorFromGroupDesc' | 'lightboxImageAlt'
| 'noContactsForGroup' | 'linkDevice'
| 'failedToAddAsModerator' | 'linkPreviewDescription'
| 'failedToRemoveFromModerator' | 'linkPreviewsConfirmMessage'
| 'copyMessage' | 'linkPreviewsTitle'
| 'selectMessage' | 'linkVisitWarningMessage'
| 'editGroup' | 'linkVisitWarningTitle'
| 'editGroupName' | 'loading'
| 'updateGroupDialogTitle' | 'mainMenuEdit'
| 'showRecoveryPhrase' | 'mainMenuFile'
| 'yourSessionID' | 'mainMenuHelp'
| 'setAccountPasswordTitle' | 'mainMenuView'
| 'setAccountPasswordDescription' | 'mainMenuWindow'
| 'changeAccountPasswordTitle' | 'markAllAsRead'
| 'changeAccountPasswordDescription' | 'markUnread'
| 'removeAccountPasswordTitle'
| 'removeAccountPasswordDescription'
| 'enterPassword'
| 'confirmPassword'
| 'enterNewPassword'
| 'confirmNewPassword'
| 'showRecoveryPhrasePasswordRequest'
| 'recoveryPhraseSavePromptMain'
| 'invalidOpenGroupUrl'
| 'copiedToClipboard'
| 'passwordViewTitle'
| 'password'
| 'setPassword'
| 'changePassword'
| 'createPassword'
| 'removePassword'
| 'maxPasswordAttempts' | 'maxPasswordAttempts'
| 'typeInOldPassword' | 'maximumAttachments'
| 'invalidOldPassword' | 'media'
| 'invalidPassword' | 'mediaEmptyState'
| 'noGivenPassword' | 'mediaMessage'
| 'passwordsDoNotMatch' | 'mediaPermissionsDescription'
| 'setPasswordInvalid' | 'mediaPermissionsTitle'
| 'changePasswordInvalid'
| 'removePasswordInvalid'
| 'setPasswordTitle'
| 'changePasswordTitle'
| 'removePasswordTitle'
| 'setPasswordToastDescription'
| 'changePasswordToastDescription'
| 'removePasswordToastDescription'
| 'publicChatExists'
| 'connectToServerFail'
| 'connectingToServer'
| 'connectToServerSuccess'
| 'setPasswordFail'
| 'passwordLengthError'
| 'passwordTypeError'
| 'passwordCharacterError'
| 'remove'
| 'invalidSessionId'
| 'invalidPubkeyFormat'
| 'emptyGroupNameError'
| 'editProfileModalTitle'
| 'groupNamePlaceholder'
| 'inviteContacts'
| 'addModerators'
| 'removeModerators'
| 'addAsModerator'
| 'removeFromModerators'
| 'add'
| 'addingContacts'
| 'noContactsToAdd'
| 'noMembersInThisGroup'
| 'noModeratorsToRemove'
| 'onlyAdminCanRemoveMembers'
| 'onlyAdminCanRemoveMembersDesc'
| 'createAccount'
| 'startInTrayTitle'
| 'startInTrayDescription'
| 'yourUniqueSessionID'
| 'allUsersAreRandomly...'
| 'getStarted'
| 'createSessionID'
| 'recoveryPhrase'
| 'enterRecoveryPhrase'
| 'displayName'
| 'anonymous'
| 'removeResidueMembers'
| 'enterDisplayName'
| 'continueYourSession'
| 'linkDevice'
| 'restoreUsingRecoveryPhrase'
| 'or'
| 'ByUsingThisService...'
| 'beginYourSession'
| 'welcomeToYourSession'
| 'searchFor...'
| 'searchForContactsOnly'
| 'enterSessionID'
| 'enterSessionIDOfRecipient'
| 'message'
| 'appearanceSettingsTitle'
| 'privacySettingsTitle'
| 'notificationsSettingsTitle'
| 'audioNotificationsSettingsTitle'
| 'notificationsSettingsContent'
| 'notificationPreview'
| 'recoveryPhraseEmpty'
| 'displayNameEmpty'
| 'displayNameTooLong'
| 'members' | 'members'
| 'activeMembers' | 'message'
| 'join' | 'messageBody'
| 'joinOpenGroup' | 'messageBodyMissing'
| 'createGroup' | 'messageDeletedPlaceholder'
| 'create' | 'messageDeletionForbidden'
| 'createClosedGroupNamePrompt' | 'messageRequestAccepted'
| 'createClosedGroupPlaceholder' | 'messageRequestAcceptedOurs'
| 'openGroupURL' | 'messageRequestAcceptedOursNoName'
| 'enterAnOpenGroupURL' | 'messageRequestPending'
| 'messageRequests'
| 'messagesHeader'
| 'moreInformation'
| 'multipleJoinedTheGroup'
| 'multipleKickedFromTheGroup'
| 'multipleLeftTheGroup'
| 'mustBeApproved'
| 'nameAndMessage'
| 'nameOnly'
| 'newMessage'
| 'newMessages'
| 'next' | 'next'
| 'invalidGroupNameTooShort' | 'nicknamePlaceholder'
| 'invalidGroupNameTooLong' | 'noAudioInputFound'
| 'pickClosedGroupMember' | 'noAudioOutputFound'
| 'closedGroupMaxSize'
| 'noBlockedContacts' | 'noBlockedContacts'
| 'userAddedToModerators' | 'noCameraFound'
| 'userRemovedFromModerators' | 'noContactsForGroup'
| 'orJoinOneOfThese' | 'noContactsToAdd'
| 'helpUsTranslateSession' | 'noGivenPassword'
| 'closedGroupInviteFailTitle' | 'noMediaUntilApproved'
| 'closedGroupInviteFailTitlePlural' | 'noMembersInThisGroup'
| 'closedGroupInviteFailMessage' | 'noMessageRequestsPending'
| 'closedGroupInviteFailMessagePlural' | 'noMessagesInBlindedDisabledMsgRequests'
| 'closedGroupInviteOkText' | 'noMessagesInEverythingElse'
| 'closedGroupInviteSuccessTitlePlural' | 'noMessagesInNoteToSelf'
| 'closedGroupInviteSuccessTitle' | 'noMessagesInReadOnly'
| 'closedGroupInviteSuccessMessage' | 'noModeratorsToRemove'
| 'noNameOrMessage'
| 'noSearchResults'
| 'noteToSelf'
| 'notificationForConvo' | 'notificationForConvo'
| 'notificationForConvo_all' | 'notificationForConvo_all'
| 'notificationForConvo_disabled' | 'notificationForConvo_disabled'
| 'notificationForConvo_mentions_only' | 'notificationForConvo_mentions_only'
| 'onionPathIndicatorTitle' | 'notificationFrom'
| 'notificationMostRecent'
| 'notificationMostRecentFrom'
| 'notificationPreview'
| 'notificationSettingsDialog'
| 'notificationSubtitle'
| 'notificationsSettingsContent'
| 'notificationsSettingsTitle'
| 'oceanDarkThemeTitle'
| 'oceanLightThemeTitle'
| 'offline'
| 'ok'
| 'oneNonImageAtATimeToast'
| 'onionPathIndicatorDescription' | 'onionPathIndicatorDescription'
| 'unknownCountry' | 'onionPathIndicatorTitle'
| 'device' | 'onlyAdminCanRemoveMembers'
| 'destination' | 'onlyAdminCanRemoveMembersDesc'
| 'learnMore'
| 'linkVisitWarningTitle'
| 'linkVisitWarningMessage'
| 'open' | 'open'
| 'audioMessageAutoplayTitle' | 'openGroupInvitation'
| 'audioMessageAutoplayDescription' | 'openGroupURL'
| 'clickToTrustContact' | 'openMessageRequestInbox'
| 'trustThisContactDialogTitle' | 'openMessageRequestInboxDescription'
| 'trustThisContactDialogDescription' | 'or'
| 'orJoinOneOfThese'
| 'originalMessageNotFound'
| 'otherPlural'
| 'otherSingular'
| 'password'
| 'passwordCharacterError'
| 'passwordLengthError'
| 'passwordTypeError'
| 'passwordViewTitle'
| 'passwordsDoNotMatch'
| 'permissionsSettingsTitle'
| 'photo'
| 'pickClosedGroupMember'
| 'pinConversation' | 'pinConversation'
| 'unpinConversation' | 'pleaseWaitOpenAndOptimizeDb'
| 'markUnread' | 'previewThumbnail'
| 'showUserDetails' | 'primaryColor'
| 'sendRecoveryPhraseTitle' | 'primaryColorBlue'
| 'sendRecoveryPhraseMessage' | 'primaryColorGreen'
| 'dialogClearAllDataDeletionFailedTitle' | 'primaryColorOrange'
| 'dialogClearAllDataDeletionFailedDesc' | 'primaryColorPink'
| 'dialogClearAllDataDeletionFailedTitleQuestion' | 'primaryColorPurple'
| 'dialogClearAllDataDeletionFailedMultiple' | 'primaryColorRed'
| 'dialogClearAllDataDeletionQuestion' | 'primaryColorYellow'
| 'clearDevice' | 'privacySettingsTitle'
| 'tryAgain' | 'pruneSettingDescription'
| 'areYouSureClearDevice' | 'pruneSettingTitle'
| 'deviceOnly' | 'publicChatExists'
| 'entireAccount' | 'quoteThumbnailAlt'
| 'areYouSureDeleteDeviceOnly' | 'rateLimitReactMessage'
| 'areYouSureDeleteEntireAccount' | 'reactionListCountPlural'
| 'iAmSure' | 'reactionListCountSingular'
| 'recoveryPhraseSecureTitle' | 'reactionNotification'
| 'recoveryPhraseRevealMessage' | 'reactionPopup'
| 'reactionPopupMany'
| 'reactionPopupOne'
| 'reactionPopupThree'
| 'reactionPopupTwo'
| 'readReceiptSettingDescription'
| 'readReceiptSettingTitle'
| 'received'
| 'recoveryPhrase'
| 'recoveryPhraseEmpty'
| 'recoveryPhraseRevealButtonText' | 'recoveryPhraseRevealButtonText'
| 'notificationSubtitle' | 'recoveryPhraseRevealMessage'
| 'surveyTitle' | 'recoveryPhraseSavePromptMain'
| 'faq' | 'recoveryPhraseSecureTitle'
| 'support' | 'remove'
| 'clearAll' | 'removeAccountPasswordDescription'
| 'clearDataSettingsTitle' | 'removeAccountPasswordTitle'
| 'messageRequests' | 'removeFromModerators'
| 'blindedMsgReqsSettingTitle' | 'removeModerators'
| 'blindedMsgReqsSettingDesc' | 'removePassword'
| 'requestsSubtitle' | 'removePasswordInvalid'
| 'removePasswordTitle'
| 'removePasswordToastDescription'
| 'removeResidueMembers'
| 'replyToMessage'
| 'replyingToMessage'
| 'reportIssue'
| 'requestsPlaceholder' | 'requestsPlaceholder'
| 'hideRequestBannerDescription' | 'requestsSubtitle'
| 'incomingCallFrom' | 'resend'
| 'respondingToRequestWarning'
| 'restoreUsingRecoveryPhrase'
| 'ringing' | 'ringing'
| 'establishingConnection' | 'save'
| 'accept' | 'saveLogToDesktop'
| 'decline' | 'saved'
| 'endCall' | 'savedTheFile'
| 'permissionsSettingsTitle' | 'searchFor...'
| 'helpSettingsTitle' | 'searchForContactsOnly'
| 'cameraPermissionNeededTitle' | 'searchMessagesHeader'
| 'cameraPermissionNeeded' | 'selectMessage'
| 'unableToCall' | 'sendFailed'
| 'unableToCallTitle' | 'sendMessage'
| 'callMissed' | 'sendRecoveryPhraseMessage'
| 'callMissedTitle' | 'sendRecoveryPhraseTitle'
| 'noCameraFound' | 'sent'
| 'noAudioInputFound' | 'sessionMessenger'
| 'noAudioOutputFound' | 'setAccountPasswordDescription'
| 'callMediaPermissionsTitle' | 'setAccountPasswordTitle'
| 'callMissedCausePermission' | 'setDisplayPicture'
| 'callMissedNotApproved' | 'setPassword'
| 'callMediaPermissionsDescription' | 'setPasswordFail'
| 'callMediaPermissionsDialogContent' | 'setPasswordInvalid'
| 'callMediaPermissionsDialogTitle' | 'setPasswordTitle'
| 'setPasswordToastDescription'
| 'settingsHeader'
| 'shareBugDetails'
| 'show'
| 'showDebugLog'
| 'showRecoveryPhrase'
| 'showRecoveryPhrasePasswordRequest'
| 'showUserDetails'
| 'someOfYourDeviceUseOutdatedVersion'
| 'spellCheckDescription'
| 'spellCheckDirty'
| 'spellCheckTitle'
| 'stagedImageAttachment'
| 'stagedPreviewThumbnail'
| 'startConversation'
| 'startInTrayDescription'
| 'startInTrayTitle'
| 'startNewConversationBy...'
| 'startedACall' | 'startedACall'
| 'answeredACall' | 'support'
| 'surveyTitle'
| 'themesSettingTitle'
| 'theyChangedTheTimer'
| 'thisMonth'
| 'thisWeek'
| 'timerOption_0_seconds'
| 'timerOption_0_seconds_abbreviated'
| 'timerOption_10_seconds'
| 'timerOption_10_seconds_abbreviated'
| 'timerOption_12_hours'
| 'timerOption_12_hours_abbreviated'
| 'timerOption_1_day'
| 'timerOption_1_day_abbreviated'
| 'timerOption_1_hour'
| 'timerOption_1_hour_abbreviated'
| 'timerOption_1_minute'
| 'timerOption_1_minute_abbreviated'
| 'timerOption_1_week'
| 'timerOption_1_week_abbreviated'
| 'timerOption_2_weeks'
| 'timerOption_2_weeks_abbreviated'
| 'timerOption_30_minutes'
| 'timerOption_30_minutes_abbreviated'
| 'timerOption_30_seconds'
| 'timerOption_30_seconds_abbreviated'
| 'timerOption_5_minutes'
| 'timerOption_5_minutes_abbreviated'
| 'timerOption_5_seconds'
| 'timerOption_5_seconds_abbreviated'
| 'timerOption_6_hours'
| 'timerOption_6_hours_abbreviated'
| 'timerSetOnSync'
| 'timerSetTo'
| 'titleIsNow'
| 'to'
| 'today'
| 'tookAScreenshot'
| 'trimDatabase' | 'trimDatabase'
| 'trimDatabaseDescription'
| 'trimDatabaseConfirmationBody' | 'trimDatabaseConfirmationBody'
| 'pleaseWaitOpenAndOptimizeDb' | 'trimDatabaseDescription'
| 'messageRequestPending' | 'trustThisContactDialogDescription'
| 'messageRequestAccepted' | 'trustThisContactDialogTitle'
| 'messageRequestAcceptedOurs' | 'tryAgain'
| 'messageRequestAcceptedOursNoName' | 'typeInOldPassword'
| 'declineRequestMessage' | 'typingAlt'
| 'respondingToRequestWarning' | 'typingIndicatorsSettingDescription'
| 'hideRequestBanner' | 'typingIndicatorsSettingTitle'
| 'openMessageRequestInbox' | 'unableToCall'
| 'noMessageRequestsPending' | 'unableToCallTitle'
| 'noMediaUntilApproved' | 'unableToLoadAttachment'
| 'mustBeApproved' | 'unbanUser'
| 'unblock'
| 'unblockToSend'
| 'unblocked'
| 'unknown'
| 'unknownCountry'
| 'unpinConversation'
| 'unreadMessages'
| 'updateGroupDialogTitle'
| 'updatedTheGroup'
| 'userAddedToModerators'
| 'userBanFailed'
| 'userBanned'
| 'userRemovedFromModerators'
| 'userUnbanFailed'
| 'userUnbanned'
| 'video'
| 'videoAttachmentAlt'
| 'viewMenuResetZoom'
| 'viewMenuToggleDevTools'
| 'viewMenuToggleFullScreen'
| 'viewMenuZoomIn'
| 'viewMenuZoomOut'
| 'voiceMessage'
| 'welcomeToYourSession'
| 'windowMenuClose'
| 'windowMenuMinimize'
| 'windowMenuZoom'
| 'yesterday'
| 'you'
| 'youChangedTheTimer'
| 'youDisabledDisappearingMessages'
| 'youGotKickedFromGroup'
| 'youHaveANewFriendRequest' | 'youHaveANewFriendRequest'
| 'clearAllConfirmationTitle' | 'youLeftTheGroup'
| 'clearAllConfirmationBody' | 'yourSessionID'
| 'noMessagesInReadOnly' | 'yourUniqueSessionID'
| 'noMessagesInBlindedDisabledMsgRequests' | 'zoomFactorSettingTitle';
| 'noMessagesInNoteToSelf'
| 'noMessagesInEverythingElse'
| 'hideBanner'
| 'someOfYourDeviceUseOutdatedVersion'
| 'openMessageRequestInboxDescription'
| 'clearAllReactions'
| 'expandedReactionsText'
| 'reactionNotification'
| 'rateLimitReactMessage'
| 'otherSingular'
| 'otherPlural'
| 'reactionPopup'
| 'reactionPopupOne'
| 'reactionPopupTwo'
| 'reactionPopupThree'
| 'reactionPopupMany'
| 'reactionListCountSingular'
| 'reactionListCountPlural';

Loading…
Cancel
Save