update components to use translate util

This commit is contained in:
Abhinav 2023-03-13 21:43:41 +05:30
parent 68bb01a24b
commit c288bb329f
137 changed files with 1386 additions and 3450 deletions

View file

@ -40,6 +40,7 @@
"INCORRECT_PASSPHRASE": "Incorrect password",
"ENTER_ENC_PASSPHRASE": "Please enter a password that we can use to encrypt your data",
"PASSPHRASE_DISCLAIMER": "We don't store your password, so if you forget it, <strong>we will not be able to help you </strong>recover your data without a recovery key.",
"WELCOME_TO_ENTE": "<0>Welcome to <1/></1></0><h2>End to end encrypted photo storage and sharing</h2>",
"WHERE_YOUR_BEST_PHOTOS_LIVE": "Where your best photos live",
"KEY_GENERATION_IN_PROGRESS_MESSAGE": "Generating encryption keys...",
"PASSPHRASE_HINT": "Password",
@ -57,8 +58,8 @@
"NOTHING_HERE": "Nothing to see here yet 👀",
"UPLOAD": "Upload",
"IMPORT": "Import",
"ADD_MORE_PHOTOS": "Add more photos",
"ADD_PHOTOS": "Add photos",
"add_photos_one": "Add photos",
"add_photos_other": "Add more photos",
"SELECT_PHOTOS": "Select photos",
"FILE_UPLOAD": "File Upload",
"UPLOAD_STAGE_MESSAGE": {
@ -141,7 +142,7 @@
"CANCEL": "Cancel",
"LOGOUT": "Logout",
"DELETE_ACCOUNT": "Delete account",
"DELETE_ACCOUNT_MESSAGE": "<p> Please send an email to {{link}} from your registered email address.</p><p>Your request will be processed within 72 hours.</p>",
"DELETE_ACCOUNT_MESSAGE": "<0>Please send an email to <1>account-deletion@ente.io</1> from your registered email address.</0><1>Your request will be processed within 72 hours.</1>",
"LOGOUT_MESSAGE": "Are you sure you want to logout?",
"CHANGE": "Change",
"CHANGE_EMAIL": "Change email",
@ -203,7 +204,7 @@
"DELETE_COLLECTION_TITLE": "Delete album?",
"DELETE_COLLECTION": "Delete album",
"DELETE_COLLECTION_FAILED": "Album deletion failed, please try again",
"DELETE_COLLECTION_MESSAGE": "<p>Also delete the photos (and videos) present in this album from <1> all </1> other albums they are part of?</p>",
"DELETE_COLLECTION_MESSAGE": "Also delete the photos (and videos) present in this album from <1> all </1> other albums they are part of?",
"DELETE_PHOTOS": "Delete photos",
"KEEP_PHOTOS": "Keep photos",
"SHARE": "Share",
@ -219,7 +220,7 @@
"DOWNLOAD_COLLECTION_FAILED": "Album downloading failed, please try again",
"CREATE_ALBUM_FAILED": "Failed to create album , please try again",
"SEARCH_RESULTS": "Search results",
"SEARCH_HINT": "<span>Search for albums, dates ...</span>",
"SEARCH_HINT": "Search for albums, dates ...",
"SEARCH_TYPE": {
"COLLECTION": "Album",
"LOCATION": "Location",
@ -228,9 +229,9 @@
"THING": "Content",
"FILE_CAPTION": "Description"
},
"PHOTO_COUNT_ZERO": "No memory",
"PHOTO_COUNT_ONE": "1 memory",
"PHOTO_COUNT_MANY": "{{count}} memories",
"photos_count_zero": "No memory",
"photos_count_one": "1 memory",
"photos_count_many": "{{count}} memories",
"TERMS_AND_CONDITIONS": "<1>I agree to the <2>terms</2> and <3>privacy policy</3></1>",
"CONFIRM_PASSWORD_NOT_SAVED": "<p>I understand that if I lose my password , I may lose my data since my data is <1>end-to-end encrypted</1> with ente</p>",
"ADD_TO_COLLECTION": "Add to album",
@ -239,9 +240,9 @@
"VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD": "This video cannot be played on your browser",
"PEOPLE": "People",
"INDEXING_SCHEDULED": "indexing is scheduled...",
"ANALYZING_PHOTOS": "analyzing new photos {{nSyncedFiles}} of {nTotalFiles}} done)...",
"INDEXING_PEOPLE": "indexing people in {{nSyncedFiles}} photos...",
"INDEXING_DONE": "indexed {{nSyncedFiles}} photos",
"ANALYZING_PHOTOS": "analyzing new photos {{indexStatus.nSyncedFiles}} of {indexStatus.nTotalFiles}} done)...",
"INDEXING_PEOPLE": "indexing people in {{indexStatus.nSyncedFiles}} photos...",
"INDEXING_DONE": "indexed {{indexStatus.nSyncedFiles}} photos",
"UNIDENTIFIED_FACES": "unidentified faces",
"OBJECTS": "objects",
"TEXT": "text",
@ -307,7 +308,7 @@
"SUCCESSFULLY_EXPORTED_FILES": "Successful exports",
"FAILED_EXPORTED_FILES": "Failed exports",
"EXPORT_AGAIN": "Resync",
"RETRY_EXPORT_": "Retry failed exports",
"RETRY_EXPORT": "Retry failed exports",
"LOCAL_STORAGE_NOT_ACCESSIBLE": "Local storage not accessible",
"LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "Your browser or an addon is blocking ente from saving data into local storage. please try loading this page after switching your browsing mode.",
"RETRY": "Retry",
@ -369,8 +370,8 @@
"LEAVE_SHARED_ALBUM_TITLE": "Leave shared album?",
"LEAVE_SHARED_ALBUM_MESSAGE": "You will leave the album, and it will stop being visible to you.",
"NOT_FILE_OWNER": "You cannot delete files in a shared album",
"CONFIRM_SELF_REMOVE_MESSAGE": "<p> Selected items will be removed from this album. Items which are only in this album will be moved to Uncategorized.</p>",
"CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "<p>Some of the items you are removing were added by other people, and you will lose access to them.</p>",
"CONFIRM_SELF_REMOVE_MESSAGE": "Selected items will be removed from this album. Items which are only in this album will be moved to Uncategorized.",
"CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "Some of the items you are removing were added by other people, and you will lose access to them.",
"SORT_BY_CREATION_TIME_ASCENDING": "Oldest",
"SORT_BY_CREATION_TIME_DESCENDING": "Newest",
"SORT_BY_UPDATION_TIME_DESCENDING": "Last updated",
@ -508,7 +509,7 @@
"CURRENT_USAGE": "Current usage is {{usage}}",
"WEAK_DEVICE": "The web browser you're using is not powerful enough to encrypt your photos. Please try to log in to ente on your computer, or download the ente mobile/desktop app.",
"DRAG_AND_DROP_HINT": "Or drag and drop into the ente window",
"ASK_FOR_FEEDBACK": "<p>We'll be sorry to see you go. Are you facing some issue?</p><p>Please write to us at {{feedbackLink}},maybe there is a way we can help.</p>",
"ASK_FOR_FEEDBACK": "<p>We'll be sorry to see you go. Are you facing some issue?</p><1>Please write to us at <2>feedback@ente.io</2>, maybe there is a way we can help.</1> <p>We'll be sorry to see you go. Are you facing some issue?</p><1>Please write to us at <2>feedback@ente.io</2>, maybe there is a way we can help.</1>",
"SEND_FEEDBACK": "Yes, send feedback",
"CONFIRM_ACCOUNT_DELETION_TITLE": "Are you sure you want to delete your account?",
"CONFIRM_ACCOUNT_DELETION_MESSAGE": "<p>Your uploaded data will be scheduled for deletion, and your account will be permanently deleted.</p><p>This action is not reversible.</p>",
@ -529,15 +530,13 @@
"NAME_PLACEHOLDER": "Name...",
"ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "Cannot create albums from file/folder mix",
"ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "<p>You have dragged and dropped a mixture of files and folders.</p><p> Please provide either only files, or only folders when selecting option to create separate albums</p>",
"ADD_PHOTOS_ONE": "Add photo",
"ADD_PHOTOS_OTHER": "Add photos",
"CHOSE_THEME": "Choose theme",
"ML_SEARCH": "ML search (beta)",
"ML_SEARCH_DESCRIPTION": "<1>This will enable on-device machine learning and face search which will start analyzing your uploaded photos locally. <br /> <br /> For the first run after login or enabling this feature, it will download all images on local device to analyze them. So please only enable this if you are ok with bandwidth and local processing of all images in your photo library. <br /> <br /> If this is the first time you're enabling this, we'll also ask your permission to process face data.</1>",
"ML_SEARCH_DESCRIPTION": "This will enable on-device machine learning and face search which will start analyzing your uploaded photos locally.<1></1><2></2>For the first run after login or enabling this feature, it will download all images on local device to analyze them. So please only enable this if you are ok with bandwidth and local processing of all images in your photo library.<4></4><5></5>If this is the first time you're enabling this, we'll also ask your permission to process face data. This will enable on-device machine learning and face search which will start analyzing your uploaded photos locally.<1></1><2></2>For the first run after login or enabling this feature, it will download all images on local device to analyze them. So please only enable this if you are ok with bandwidth and local processing of all images in your photo library.<4></4><5></5>If this is the first time you're enabling this, we'll also ask your permission to process face data.",
"ML_MORE_DETAILS": "More details",
"ENABLE_FACE_SEARCH": "Enable face search",
"ENABLE_FACE_SEARCH_TITLE": "Enable face search?",
"ENABLE_FACE_SEARCH_DESCRIPTION": "<1>If you enable face search, ente will extract face geometry from your photos. This will happen on your device, and any generated biometric data will be end-to-encrypted.<br /><br /><2> Please click here for more details about this feature in our privacy policy </2></1>",
"ENABLE_FACE_SEARCH_DESCRIPTION": "If you enable face search, ente will extract face geometry from your photos. This will happen on your device, and any generated biometric data will be end-to-encrypted.<br ></br><br></br><3> Please click here for more details about this feature in our privacy policy </3>",
"DISABLE_BETA": "Disable beta",
"DISABLE_FACE_SEARCH": "Disable face search",
"DISABLE_FACE_SEARCH_TITLE": "Disable face search?",
@ -546,9 +545,10 @@
"FACE_SEARCH_CONFIRMATION": "I understand, and wish to allow ente to process face geometry",
"LABS": "Labs",
"YOURS": "yours",
"PASSPHRASE_STRENGTH": "Password strength: {{strength}}",
"PASSPHRASE_STRENGTH": "Password strength: {{passwordStrength}}",
"PREFERENCES": "Preferences",
"LANGUAGE": "Language",
"EXPORT_DIRECTORY_DOES_NOT_EXIST": "Invalid export directory",
"EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "<p>The export directory you have selected does not exist.</p><p> Please select a valid directory.</p>"
"EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "<p>The export directory you have selected does not exist.</p><p> Please select a valid directory.</p>",
"SUBSCRIPTION_VERIFICATION_ERROR": "Subscription verification failed"
}

View file

@ -1,6 +1,5 @@
import React, { useContext, useEffect, useState } from 'react';
import constants from 'utils/strings/constants';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import { AppContext } from 'pages/_app';
import { KeyAttributes, User } from 'types/user';
@ -9,6 +8,7 @@ import VerifyMasterPasswordForm, {
} from 'components/VerifyMasterPasswordForm';
import { Dialog, Stack, Typography } from '@mui/material';
import { logError } from 'utils/sentry';
import { useTranslation } from 'react-i18next';
interface Iprops {
open: boolean;
@ -21,15 +21,16 @@ export default function AuthenticateUserModal({
onClose,
onAuthenticate,
}: Iprops) {
const { t } = useTranslation();
const { setDialogMessage } = useContext(AppContext);
const [user, setUser] = useState<User>();
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
const somethingWentWrong = () =>
setDialogMessage({
title: constants.ERROR,
title: t('ERROR'),
close: { variant: 'danger' },
content: constants.UNKNOWN_ERROR,
content: t('UNKNOWN_ERROR'),
});
useEffect(() => {
@ -74,10 +75,10 @@ export default function AuthenticateUserModal({
PaperProps={{ sx: { p: 1, maxWidth: '346px' } }}>
<Stack spacing={3} p={1.5}>
<Typography variant="h3" px={1} py={0.5} fontWeight={'bold'}>
{constants.PASSWORD}
{t('PASSWORD')}
</Typography>
<VerifyMasterPasswordForm
buttonText={constants.AUTHENTICATE}
buttonText={t('AUTHENTICATE')}
callback={useMasterPassword}
user={user}
keyAttributes={keyAttributes}

View file

@ -1,7 +1,6 @@
import { Formik, FormikHelpers } from 'formik';
import React, { useRef, useState } from 'react';
import * as Yup from 'yup';
import constants from 'utils/strings/constants';
import SubmitButton from 'components/SubmitButton';
import router from 'next/router';
import { changeEmail, sendOTTForEmailChange } from 'services/userService';
@ -12,6 +11,7 @@ import Container from './Container';
import LinkButton from './pages/gallery/LinkButton';
import FormPaperFooter from './Form/FormPaper/Footer';
import { sleep } from 'utils/common';
import { useTranslation } from 'react-i18next';
interface formValues {
email: string;
@ -19,6 +19,7 @@ interface formValues {
}
function ChangeEmailForm() {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [ottInputVisible, setShowOttInputVisibility] = useState(false);
const ottInputRef = useRef(null);
@ -40,7 +41,7 @@ function ChangeEmailForm() {
ottInputRef.current?.focus();
}, 250);
} catch (e) {
setFieldError('email', `${constants.EMAIl_ALREADY_OWNED}`);
setFieldError('email', t('EMAIl_ALREADY_OWNED}'));
}
setLoading(false);
};
@ -59,7 +60,7 @@ function ChangeEmailForm() {
router.push(PAGES.GALLERY);
} catch (e) {
setLoading(false);
setFieldError('ott', `${constants.INCORRECT_CODE}`);
setFieldError('ott', t('INCORRECT_CODE'));
}
};
@ -70,11 +71,9 @@ function ChangeEmailForm() {
initialValues={{ email: '' }}
validationSchema={Yup.object().shape({
email: Yup.string()
.email(constants.EMAIL_ERROR)
.required(constants.REQUIRED),
ott:
ottInputVisible &&
Yup.string().required(constants.REQUIRED),
.email(t('EMAIL_ERROR'))
.required(t('REQUIRED')),
ott: ottInputVisible && Yup.string().required(t('REQUIRED')),
})}
validateOnChange={false}
validateOnBlur={false}
@ -85,7 +84,7 @@ function ChangeEmailForm() {
<Alert
color="accent"
onClose={() => setShowMessage(false)}>
{constants.EMAIL_SENT({ email })}
{t('EMAIL_SENT', { email })}
</Alert>
)}
<form noValidate onSubmit={handleSubmit}>
@ -96,7 +95,7 @@ function ChangeEmailForm() {
readOnly: ottInputVisible,
}}
type="email"
label={constants.ENTER_EMAIL}
label={t('ENTER_EMAIL')}
value={values.email}
onChange={handleChange('email')}
error={Boolean(errors.email)}
@ -108,7 +107,7 @@ function ChangeEmailForm() {
<TextField
fullWidth
type="text"
label={constants.ENTER_OTT}
label={t('ENTER_OTT')}
value={values.ott}
onChange={handleChange('ott')}
error={Boolean(errors.ott)}
@ -122,8 +121,8 @@ function ChangeEmailForm() {
loading={loading}
buttonText={
!ottInputVisible
? constants.SEND_OTT
: constants.VERIFY
? t('SEND_OTT')
: t('VERIFY')
}
/>
</Container>
@ -138,11 +137,11 @@ function ChangeEmailForm() {
onClick={() =>
setShowOttInputVisibility(false)
}>
{constants.CHANGE_EMAIL}?
{t('CHANGE_EMAIL')}?
</LinkButton>
)}
<LinkButton onClick={goToGallery}>
{constants.GO_BACK}
{t('GO_BACK')}
</LinkButton>
</FormPaperFooter>
</>

View file

@ -1,5 +1,4 @@
import React, { useState } from 'react';
import constants from 'utils/strings/constants';
import DoneIcon from '@mui/icons-material/Done';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import {
@ -8,6 +7,7 @@ import {
SvgIconProps,
Tooltip,
} from '@mui/material';
import { useTranslation } from 'react-i18next';
export default function CopyButton({
code,
@ -18,6 +18,7 @@ export default function CopyButton({
color?: IconButtonProps['color'];
size?: SvgIconProps['fontSize'];
}) {
const { t } = useTranslation();
const [copied, setCopied] = useState<boolean>(false);
const copyToClipboardHelper = (text: string) => () => {
@ -29,7 +30,7 @@ export default function CopyButton({
<Tooltip
arrow
open={copied}
title={constants.COPIED}
title={t('COPIED')}
PopperProps={{ sx: { zIndex: 2000 } }}>
<IconButton onClick={copyToClipboardHelper(code)} color={color}>
{copied ? (

View file

@ -1,22 +1,24 @@
import React from 'react';
import { COLLECTION_SORT_BY } from 'constants/collection';
import constants from 'utils/strings/constants';
import SortByOptionCreator from './optionCreator';
import { CollectionSortProps } from '.';
import { useTranslation } from 'react-i18next';
export default function CollectionSortOptions(props: CollectionSortProps) {
const { t } = useTranslation();
const SortByOption = SortByOptionCreator(props);
return (
<>
<SortByOption sortBy={COLLECTION_SORT_BY.NAME}>
{constants.SORT_BY_NAME}
{t('SORT_BY_NAME')}
</SortByOption>
<SortByOption sortBy={COLLECTION_SORT_BY.CREATION_TIME_ASCENDING}>
{constants.SORT_BY_CREATION_TIME_ASCENDING}
{t('SORT_BY_CREATION_TIME_ASCENDING')}
</SortByOption>
<SortByOption sortBy={COLLECTION_SORT_BY.UPDATION_TIME_DESCENDING}>
{constants.SORT_BY_UPDATION_TIME_DESCENDING}
{t('SORT_BY_UPDATION_TIME_DESCENDING')}
</SortByOption>
</>
);

View file

@ -1,9 +1,9 @@
import { Typography } from '@mui/material';
import constants from 'utils/strings/constants';
import React from 'react';
import CollectionCard from '../CollectionCard';
import { CollectionSummary } from 'types/collection';
import { AllCollectionTile, AllCollectionTileText } from '../styledComponents';
import { useTranslation } from 'react-i18next';
interface Iprops {
collectionSummary: CollectionSummary;
@ -14,6 +14,7 @@ export default function AllCollectionCard({
onCollectionClick,
collectionSummary,
}: Iprops) {
const { t } = useTranslation();
return (
<CollectionCard
collectionTile={AllCollectionTile}
@ -22,7 +23,7 @@ export default function AllCollectionCard({
<AllCollectionTileText>
<Typography>{collectionSummary.name}</Typography>
<Typography variant="body2" color="text.secondary">
{constants.PHOTO_COUNT(collectionSummary.fileCount)}
{t('photos_count', { count: collectionSummary.fileCount })}
</Typography>
</AllCollectionTileText>
</CollectionCard>

View file

@ -6,8 +6,8 @@ import {
IconButtonWithBG,
} from 'components/Container';
import CollectionSort from 'components/Collections/AllCollections/CollectionSort';
import constants from 'utils/strings/constants';
import Close from '@mui/icons-material/Close';
import { useTranslation } from 'react-i18next';
export default function AllCollectionsHeader({
onClose,
@ -15,16 +15,16 @@ export default function AllCollectionsHeader({
collectionSortBy,
setCollectionSortBy,
}) {
const { t } = useTranslation();
return (
<DialogTitle>
<FlexWrapper>
<FluidContainer mr={1.5}>
<Box>
<Typography variant="h3">
{constants.ALL_ALBUMS}
</Typography>
<Typography variant="h3">{t('ALL_ALBUMS')}</Typography>
<Typography variant="body2" color={'text.secondary'}>
{`${collectionCount} ${constants.ALBUMS}`}
{`${collectionCount} ${t('ALBUMS')}`}
</Typography>
</Box>
</FluidContainer>

View file

@ -1,7 +1,7 @@
import { Box, Typography } from '@mui/material';
import { FlexWrapper } from 'components/Container';
import React from 'react';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
interface Iprops {
name: string;
fileCount: number;
@ -9,13 +9,14 @@ interface Iprops {
}
export function CollectionInfo({ name, fileCount, endIcon }: Iprops) {
const { t } = useTranslation();
return (
<div>
<Typography variant="h3">{name}</Typography>
<FlexWrapper>
<Typography variant="body2" color="text.secondary">
{constants.PHOTO_COUNT(fileCount)}
{t('photos_count', { count: fileCount })}
</Typography>
{endIcon && (
<Box

View file

@ -1,6 +1,5 @@
import ScrollButton from 'components/Collections/CollectionListBar/ScrollButton';
import React, { useContext, useEffect } from 'react';
import constants from 'utils/strings/constants';
import { ALL_SECTION, COLLECTION_SORT_BY } from 'constants/collection';
import { Box, IconButton, Typography } from '@mui/material';
import {
@ -16,6 +15,7 @@ import ExpandMore from '@mui/icons-material/ExpandMore';
import { AppContext } from 'pages/_app';
import { CollectionSummary } from 'types/collection';
import CollectionSort from '../AllCollections/CollectionSort';
import { useTranslation } from 'react-i18next';
interface IProps {
activeCollection?: number;
@ -34,6 +34,8 @@ export default function CollectionListBar(props: IProps) {
showAllCollections,
} = props;
const { t } = useTranslation();
const appContext = useContext(AppContext);
const windowSize = useWindowSize();
@ -62,7 +64,7 @@ export default function CollectionListBar(props: IProps) {
return (
<CollectionListBarWrapper>
<SpaceBetweenFlex mb={1}>
<Typography>{constants.ALBUMS}</Typography>
<Typography>{t('ALBUMS')}</Typography>
{appContext.isMobile && (
<Box display="flex" alignItems={'center'} gap={1}>
<CollectionSort

View file

@ -1,10 +1,10 @@
import React from 'react';
import constants from 'utils/strings/constants';
import SingleInputForm, {
SingleInputFormProps,
} from 'components/SingleInputForm';
import DialogBoxBase from 'components/DialogBox/base';
import { DialogContent, DialogTitle } from '@mui/material';
import { useTranslation } from 'react-i18next';
export interface CollectionNamerAttributes {
callback: (name: string) => void;
@ -24,6 +24,7 @@ interface Props {
}
export default function CollectionNamer({ attributes, ...props }: Props) {
const { t } = useTranslation();
if (!attributes) {
return <></>;
}
@ -35,7 +36,7 @@ export default function CollectionNamer({ attributes, ...props }: Props) {
attributes.callback(albumName);
props.onHide();
} catch (e) {
setFieldError(constants.UNKNOWN_ERROR);
setFieldError(t('UNKNOWN_ERROR'));
}
};
@ -47,7 +48,7 @@ export default function CollectionNamer({ attributes, ...props }: Props) {
callback={onSubmit}
fieldType="text"
buttonText={attributes.buttonText}
placeholder={constants.ENTER_ALBUM_NAME}
placeholder={t('ENTER_ALBUM_NAME')}
initialValue={attributes.autoFilledName}
submitButtonProps={{ sx: { mt: 1, mb: 2 } }}
secondaryButtonAction={props.onHide}

View file

@ -4,10 +4,10 @@ import React from 'react';
import EditIcon from '@mui/icons-material/Edit';
import PeopleIcon from '@mui/icons-material/People';
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
import constants from 'utils/strings/constants';
import { CollectionActions } from '.';
import Unarchive from '@mui/icons-material/Unarchive';
import ArchiveOutlined from '@mui/icons-material/ArchiveOutlined';
import { useTranslation } from 'react-i18next';
interface Iprops {
IsArchived: boolean;
@ -21,6 +21,7 @@ export function AlbumCollectionOption({
IsArchived,
handleCollectionAction,
}: Iprops) {
const { t } = useTranslation();
return (
<>
<OverflowMenuOption
@ -29,7 +30,7 @@ export function AlbumCollectionOption({
false
)}
startIcon={<EditIcon />}>
{constants.RENAME_COLLECTION}
{t('RENAME_COLLECTION')}
</OverflowMenuOption>
{IsArchived ? (
<OverflowMenuOption
@ -37,13 +38,13 @@ export function AlbumCollectionOption({
CollectionActions.UNARCHIVE
)}
startIcon={<Unarchive />}>
{constants.UNARCHIVE_COLLECTION}
{t('UNARCHIVE_COLLECTION')}
</OverflowMenuOption>
) : (
<OverflowMenuOption
onClick={handleCollectionAction(CollectionActions.ARCHIVE)}
startIcon={<ArchiveOutlined />}>
{constants.ARCHIVE_COLLECTION}
{t('ARCHIVE_COLLECTION')}
</OverflowMenuOption>
)}
<OverflowMenuOption
@ -52,7 +53,7 @@ export function AlbumCollectionOption({
CollectionActions.CONFIRM_DELETE,
false
)}>
{constants.DELETE_COLLECTION}
{t('DELETE_COLLECTION')}
</OverflowMenuOption>
<OverflowMenuOption
onClick={handleCollectionAction(
@ -60,7 +61,7 @@ export function AlbumCollectionOption({
false
)}
startIcon={<PeopleIcon />}>
{constants.SHARE_COLLECTION}
{t('SHARE_COLLECTION')}
</OverflowMenuOption>
</>
);

View file

@ -2,9 +2,8 @@ import { OverflowMenuOption } from 'components/OverflowMenu/option';
import React from 'react';
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
import constants from 'utils/strings/constants';
import { CollectionActions } from '.';
import { t } from 'i18next';
interface Iprops {
handleCollectionAction: (
action: CollectionActions,
@ -15,7 +14,7 @@ interface Iprops {
export function OnlyDownloadCollectionOption({
handleCollectionAction,
downloadOptionText = constants.DOWNLOAD,
downloadOptionText = t('DOWNLOAD'),
}: Iprops) {
return (
<OverflowMenuOption

View file

@ -2,8 +2,8 @@ import { CollectionActions } from '..';
import React from 'react';
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
import { IconButton, Tooltip } from '@mui/material';
import constants from 'utils/strings/constants';
import { CollectionSummaryType } from 'constants/collection';
import { useTranslation } from 'react-i18next';
interface Iprops {
handleCollectionAction: (
action: CollectionActions,
@ -16,15 +16,17 @@ export function DownloadQuickOption({
handleCollectionAction,
collectionSummaryType,
}: Iprops) {
const { t } = useTranslation();
return (
<Tooltip
title={
collectionSummaryType === CollectionSummaryType.favorites
? constants.DOWNLOAD_FAVORITES
? t('DOWNLOAD_FAVORITES')
: collectionSummaryType ===
CollectionSummaryType.uncategorized
? constants.DOWNLOAD_UNCATEGORIZED
: constants.DOWNLOAD_COLLECTION
? t('DOWNLOAD_UNCATEGORIZED')
: t('DOWNLOAD_COLLECTION')
}>
<IconButton
onClick={handleCollectionAction(

View file

@ -2,7 +2,7 @@ import { CollectionActions } from '..';
import React from 'react';
import { IconButton, Tooltip } from '@mui/material';
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
interface Iprops {
handleCollectionAction: (
@ -12,8 +12,10 @@ interface Iprops {
}
export function EmptyTrashQuickOption({ handleCollectionAction }: Iprops) {
const { t } = useTranslation();
return (
<Tooltip title={constants.EMPTY_TRASH}>
<Tooltip title={t('EMPTY_TRASH')}>
<IconButton
onClick={handleCollectionAction(
CollectionActions.CONFIRM_EMPTY_TRASH,

View file

@ -2,8 +2,8 @@ import { CollectionActions } from '..';
import React from 'react';
import PeopleIcon from '@mui/icons-material/People';
import { IconButton, Tooltip } from '@mui/material';
import constants from 'utils/strings/constants';
import { CollectionSummaryType } from 'constants/collection';
import { useTranslation } from 'react-i18next';
interface Iprops {
handleCollectionAction: (
@ -17,17 +17,19 @@ export function ShareQuickOption({
handleCollectionAction,
collectionSummaryType,
}: Iprops) {
const { t } = useTranslation();
return (
<Tooltip
title={
/*: collectionSummaryType ===
CollectionSummaryType.incomingShare
? constants.SHARING_DETAILS*/
? t("SHARING_DETAILS*/
collectionSummaryType === CollectionSummaryType.outgoingShare ||
collectionSummaryType ===
CollectionSummaryType.sharedOnlyViaLink
? constants.MODIFY_SHARING
: constants.SHARE_COLLECTION
? t('MODIFY_SHARING')
: t('SHARE_COLLECTION')
}>
<IconButton
onClick={handleCollectionAction(

View file

@ -1,8 +1,8 @@
import { OverflowMenuOption } from 'components/OverflowMenu/option';
import React from 'react';
import LogoutIcon from '@mui/icons-material/Logout';
import constants from 'utils/strings/constants';
import { CollectionActions } from '.';
import { useTranslation } from 'react-i18next';
interface Iprops {
handleCollectionAction: (
@ -12,6 +12,7 @@ interface Iprops {
}
export function SharedCollectionOption({ handleCollectionAction }: Iprops) {
const { t } = useTranslation();
return (
<OverflowMenuOption
startIcon={<LogoutIcon />}
@ -19,7 +20,7 @@ export function SharedCollectionOption({ handleCollectionAction }: Iprops) {
CollectionActions.CONFIRM_LEAVE_SHARED_ALBUM,
false
)}>
{constants.LEAVE_ALBUM}
{t('LEAVE_ALBUM')}
</OverflowMenuOption>
);
}

View file

@ -2,8 +2,8 @@ import { OverflowMenuOption } from 'components/OverflowMenu/option';
import React from 'react';
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
import constants from 'utils/strings/constants';
import { CollectionActions } from '.';
import { useTranslation } from 'react-i18next';
interface Iprops {
handleCollectionAction: (
@ -13,6 +13,7 @@ interface Iprops {
}
export function TrashCollectionOption({ handleCollectionAction }: Iprops) {
const { t } = useTranslation();
return (
<OverflowMenuOption
color="danger"
@ -21,7 +22,7 @@ export function TrashCollectionOption({ handleCollectionAction }: Iprops) {
CollectionActions.CONFIRM_EMPTY_TRASH,
false
)}>
{constants.EMPTY_TRASH}
{t('EMPTY_TRASH')}
</OverflowMenuOption>
);
}

View file

@ -6,7 +6,6 @@ import {
changeCollectionVisibility,
downloadAllCollectionFiles,
} from 'utils/collection';
import constants from 'utils/strings/constants';
import { SetCollectionNamerAttributes } from '../CollectionNamer';
import { Collection } from 'types/collection';
import { IsArchived } from 'utils/magicMetadata';
@ -22,6 +21,7 @@ import { OnlyDownloadCollectionOption } from './OnlyDownloadCollectionOption';
import { QuickOptions } from './QuickOptions';
import MoreHoriz from '@mui/icons-material/MoreHoriz';
import { HorizontalFlex } from 'components/Container';
import { Trans, useTranslation } from 'react-i18next';
interface CollectionOptionsProps {
setCollectionNamerAttributes: SetCollectionNamerAttributes;
@ -57,6 +57,8 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
showCollectionShareModal,
} = props;
const { t } = useTranslation();
const { startLoading, finishLoading, setDialogMessage } =
useContext(AppContext);
const { syncWithRemote } = useContext(GalleryContext);
@ -124,8 +126,8 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
await callback(...args);
} catch (e) {
setDialogMessage({
title: constants.ERROR,
content: constants.UNKNOWN_ERROR,
title: t('ERROR'),
content: t('UNKNOWN_ERROR'),
close: { variant: 'danger' },
});
} finally {
@ -176,8 +178,8 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
const showRenameCollectionModal = () => {
setCollectionNamerAttributes({
title: constants.RENAME_COLLECTION,
buttonText: constants.RENAME,
title: t('RENAME_COLLECTION'),
buttonText: t('RENAME'),
autoFilledName: activeCollection.name,
callback: handleCollectionAction(CollectionActions.RENAME),
});
@ -185,69 +187,81 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
const confirmDeleteCollection = () => {
setDialogMessage({
title: constants.DELETE_COLLECTION_TITLE,
content: constants.DELETE_COLLECTION_MESSAGE(),
title: t('DELETE_COLLECTION_TITLE'),
content: (
<Trans>
Also delete the photos (and videos) present in this album
from
<span style={{ color: '#fff' }}> all </span> other albums
they are part of?
</Trans>
),
proceed: {
text: constants.DELETE_PHOTOS,
text: t('DELETE_PHOTOS'),
action: handleCollectionAction(
CollectionActions.DELETE_WITH_FILES
),
variant: 'danger',
},
secondary: {
text: constants.KEEP_PHOTOS,
text: t('KEEP_PHOTOS'),
action: handleCollectionAction(
CollectionActions.DELETE_BUT_KEEP_FILES
),
variant: 'primary',
},
close: {
text: constants.CANCEL,
text: t('CANCEL'),
},
});
};
const confirmDownloadCollection = () => {
setDialogMessage({
title: constants.DOWNLOAD_COLLECTION,
content: constants.DOWNLOAD_COLLECTION_MESSAGE(),
title: t('DOWNLOAD_COLLECTION'),
content: (
<Trans>
<p>Are you sure you want to download the complete album?</p>
<p>All files will be queued for download sequentially</p>
</Trans>
),
proceed: {
text: constants.DOWNLOAD,
text: t('DOWNLOAD'),
action: handleCollectionAction(CollectionActions.DOWNLOAD),
variant: 'accent',
},
close: {
text: constants.CANCEL,
text: t('CANCEL'),
},
});
};
const confirmEmptyTrash = () =>
setDialogMessage({
title: constants.EMPTY_TRASH_TITLE,
content: constants.EMPTY_TRASH_MESSAGE,
title: t('EMPTY_TRASH_TITLE'),
content: t('EMPTY_TRASH_MESSAGE'),
proceed: {
action: handleCollectionAction(CollectionActions.EMPTY_TRASH),
text: constants.EMPTY_TRASH,
text: t('EMPTY_TRASH'),
variant: 'danger',
},
close: { text: constants.CANCEL },
close: { text: t('CANCEL') },
});
const confirmLeaveSharedAlbum = () => {
setDialogMessage({
title: constants.LEAVE_SHARED_ALBUM_TITLE,
content: constants.LEAVE_SHARED_ALBUM_MESSAGE,
title: t('LEAVE_SHARED_ALBUM_TITLE'),
content: t('LEAVE_SHARED_ALBUM_MESSAGE'),
proceed: {
text: constants.LEAVE_SHARED_ALBUM,
text: t('LEAVE_SHARED_ALBUM'),
action: handleCollectionAction(
CollectionActions.LEAVE_SHARED_ALBUM
),
variant: 'danger',
},
close: {
text: constants.CANCEL,
text: t('CANCEL'),
},
});
};
@ -270,13 +284,13 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
CollectionSummaryType.favorites ? (
<OnlyDownloadCollectionOption
handleCollectionAction={handleCollectionAction}
downloadOptionText={constants.DOWNLOAD_FAVORITES}
downloadOptionText={t('DOWNLOAD_FAVORITES')}
/>
) : collectionSummaryType ===
CollectionSummaryType.uncategorized ? (
<OnlyDownloadCollectionOption
handleCollectionAction={handleCollectionAction}
downloadOptionText={constants.DOWNLOAD_UNCATEGORIZED}
downloadOptionText={t('DOWNLOAD_UNCATEGORIZED')}
/>
) : collectionSummaryType ===
CollectionSummaryType.incomingShare ? (

View file

@ -5,8 +5,8 @@ import {
} from 'components/Collections/styledComponents';
import React from 'react';
import { styled } from '@mui/material';
import constants from 'utils/strings/constants';
import { CenteredFlex, Overlay } from 'components/Container';
import { useTranslation } from 'react-i18next';
const ImageContainer = styled(Overlay)`
display: flex;
@ -18,13 +18,15 @@ interface Iprops {
}
export default function AddCollectionButton({ showNextModal }: Iprops) {
const { t } = useTranslation();
return (
<CollectionCard
collectionTile={AllCollectionTile}
onClick={() => showNextModal()}
latestFile={null}>
<AllCollectionTileText>
{constants.CREATE_COLLECTION}
{t('CREATE_COLLECTION')}
</AllCollectionTileText>
<ImageContainer>
<CenteredFlex>+</CenteredFlex>

View file

@ -3,14 +3,15 @@ import SingleInputForm, {
} from 'components/SingleInputForm';
import { GalleryContext } from 'pages/gallery';
import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { shareCollection } from 'services/collectionService';
import { User } from 'types/user';
import { handleSharingErrors } from 'utils/error/ui';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import constants from 'utils/strings/constants';
import { CollectionShareSharees } from './sharees';
export default function EmailShare({ collection }) {
const { t } = useTranslation();
const galleryContext = useContext(GalleryContext);
const collectionShare: SingleInputFormProps['callback'] = async (
@ -21,11 +22,11 @@ export default function EmailShare({ collection }) {
try {
const user: User = getData(LS_KEYS.USER);
if (email === user.email) {
setFieldError(constants.SHARE_WITH_SELF);
setFieldError(t('SHARE_WITH_SELF'));
} else if (
collection?.sharees?.find((value) => value.email === email)
) {
setFieldError(constants.ALREADY_SHARED(email));
setFieldError(t('ALREADY_SHARED', { email }));
} else {
await shareCollection(collection, email);
await galleryContext.syncWithRemote(false, true);
@ -40,9 +41,9 @@ export default function EmailShare({ collection }) {
<>
<SingleInputForm
callback={collectionShare}
placeholder={constants.ENTER_EMAIL}
placeholder={t('ENTER_EMAIL')}
fieldType="email"
buttonText={constants.SHARE}
buttonText={t('SHARE')}
submitButtonProps={{
size: 'medium',
sx: { mt: 1, mb: 2 },

View file

@ -1,6 +1,5 @@
import EmailShare from './emailShare';
import React, { useContext } from 'react';
import constants from 'utils/strings/constants';
import { Collection } from 'types/collection';
import DialogTitleWithCloseButton, {
dialogCloseHandler,
@ -11,6 +10,7 @@ import { Divider } from '@mui/material';
import { CollectionShareContainer } from './container';
import PublicShare from './publicShare';
import { AppContext } from 'pages/_app';
import { useTranslation } from 'react-i18next';
interface Props {
open: boolean;
@ -19,6 +19,7 @@ interface Props {
}
function CollectionShare(props: Props) {
const { t } = useTranslation();
const { isMobile } = useContext(AppContext);
const handleClose = dialogCloseHandler({
onClose: props.onClose,
@ -35,7 +36,7 @@ function CollectionShare(props: Props) {
onClose={handleClose}
fullScreen={isMobile}>
<DialogTitleWithCloseButton onClose={handleClose}>
{constants.SHARE_COLLECTION}
{t('SHARE_COLLECTION')}
</DialogTitleWithCloseButton>
<DialogContent>
<EmailShare collection={props.collection} />

View file

@ -3,13 +3,13 @@ import { FlexWrapper } from 'components/Container';
import { GalleryContext } from 'pages/gallery';
import { AppContext } from 'pages/_app';
import React, { useContext, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
createShareableURL,
deleteShareableURL,
} from 'services/collectionService';
import { Collection, PublicURL } from 'types/collection';
import { handleSharingErrors } from 'utils/error/ui';
import constants from 'utils/strings/constants';
import PublicShareSwitch from './switch';
interface Iprops {
collection: Collection;
@ -22,6 +22,8 @@ export default function PublicShareControl({
publicShareActive,
setPublicShareProp,
}: Iprops) {
const { t } = useTranslation();
const appContext = useContext(AppContext);
const galleryContext = useContext(GalleryContext);
const [sharableLinkError, setSharableLinkError] = useState(null);
@ -56,11 +58,11 @@ export default function PublicShareControl({
const confirmDisablePublicSharing = () => {
appContext.setDialogMessage({
title: constants.DISABLE_PUBLIC_SHARING,
content: constants.DISABLE_PUBLIC_SHARING_MESSAGE,
close: { text: constants.CANCEL },
title: t('DISABLE_PUBLIC_SHARING'),
content: t('DISABLE_PUBLIC_SHARING_MESSAGE'),
close: { text: t('CANCEL') },
proceed: {
text: constants.DISABLE,
text: t('DISABLE'),
action: disablePublicSharing,
variant: 'danger',
},
@ -78,7 +80,7 @@ export default function PublicShareControl({
return (
<Box mt={3}>
<FlexWrapper>
<FlexWrapper>{constants.PUBLIC_SHARING}</FlexWrapper>
<FlexWrapper>{t('PUBLIC_SHARING')}</FlexWrapper>
<PublicShareSwitch
color="accent"

View file

@ -1,10 +1,10 @@
import { Box, Typography } from '@mui/material';
import React from 'react';
import { useTranslation } from 'react-i18next';
import Select from 'react-select';
import { DropdownStyle } from 'styles/dropdown';
import { Collection, PublicURL, UpdatePublicURL } from 'types/collection';
import { getDeviceLimitOptions } from 'utils/collection';
import constants from 'utils/strings/constants';
import { OptionWithDivider } from './selectComponents/OptionWithDivider';
interface Iprops {
@ -18,6 +18,8 @@ export function ManageDeviceLimit({
collection,
updatePublicShareURLHelper,
}: Iprops) {
const { t } = useTranslation();
const updateDeviceLimit = async (newLimit: number) => {
return updatePublicShareURLHelper({
collectionID: collection.id,
@ -27,7 +29,7 @@ export function ManageDeviceLimit({
return (
<Box>
<Typography mb={0.5}>{constants.LINK_DEVICE_LIMIT}</Typography>
<Typography mb={0.5}>{t('LINK_DEVICE_LIMIT')}</Typography>
<Select
menuPosition="fixed"
options={getDeviceLimitOptions()}

View file

@ -1,8 +1,8 @@
import { Box, Typography } from '@mui/material';
import { AppContext } from 'pages/_app';
import React, { useContext } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { PublicURL, Collection, UpdatePublicURL } from 'types/collection';
import constants from 'utils/strings/constants';
import PublicShareSwitch from '../switch';
interface Iprops {
@ -16,6 +16,8 @@ export function ManageDownloadAccess({
updatePublicShareURLHelper,
collection,
}: Iprops) {
const { t } = useTranslation();
const appContext = useContext(AppContext);
const handleFileDownloadSetting = () => {
@ -31,11 +33,22 @@ export function ManageDownloadAccess({
const disableFileDownload = () => {
appContext.setDialogMessage({
title: constants.DISABLE_FILE_DOWNLOAD,
content: constants.DISABLE_FILE_DOWNLOAD_MESSAGE(),
close: { text: constants.CANCEL },
title: t('DISABLE_FILE_DOWNLOAD'),
content: (
<Trans>
<p>
Are you sure that you want to disable the download
button for files?{' '}
</p>{' '}
<p>
Viewers can still take screenshots or save a copy of
your photos using external tools{' '}
</p>
</Trans>
),
close: { text: t('CANCEL') },
proceed: {
text: constants.DISABLE,
text: t('DISABLE'),
action: () =>
updatePublicShareURLHelper({
collectionID: collection.id,
@ -47,7 +60,7 @@ export function ManageDownloadAccess({
};
return (
<Box>
<Typography mb={0.5}>{constants.FILE_DOWNLOAD}</Typography>
<Typography mb={0.5}>{t('FILE_DOWNLOAD')}</Typography>
<PublicShareSwitch
checked={publicShareProp?.enableDownload ?? true}
onChange={handleFileDownloadSetting}

View file

@ -7,7 +7,6 @@ import React, { useContext, useState } from 'react';
import { updateShareableURL } from 'services/collectionService';
import { Collection, PublicURL, UpdatePublicURL } from 'types/collection';
import { sleep } from 'utils/common';
import constants from 'utils/strings/constants';
import {
ManageSectionLabel,
ManageSectionOptions,
@ -16,6 +15,7 @@ import { ManageDownloadAccess } from './downloadAccess';
import { handleSharingErrors } from 'utils/error/ui';
import { SetPublicShareProp } from 'types/publicCollection';
import { ManagePublicCollect } from './publicCollect';
import { useTranslation } from 'react-i18next';
interface Iprops {
publicShareProp: PublicURL;
@ -28,6 +28,7 @@ export default function PublicShareManage({
collection,
setPublicShareProp,
}: Iprops) {
const { t } = useTranslation();
const galleryContext = useContext(GalleryContext);
const [sharableLinkError, setSharableLinkError] = useState(null);
@ -59,7 +60,7 @@ export default function PublicShareManage({
<>
<details>
<ManageSectionLabel onClick={scrollToEnd}>
{constants.MANAGE_LINK}
{t('MANAGE_LINK')}
</ManageSectionLabel>
<ManageSectionOptions>
<Stack spacing={1.5}>

View file

@ -4,7 +4,8 @@ import Select from 'react-select';
import { linkExpiryStyle } from 'styles/linkExpiry';
import { PublicURL, Collection, UpdatePublicURL } from 'types/collection';
import { shareExpiryOptions } from 'utils/collection';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import { formatDateTime } from 'utils/time/format';
import { OptionWithDivider } from './selectComponents/OptionWithDivider';
@ -19,6 +20,7 @@ export function ManageLinkExpiry({
collection,
updatePublicShareURLHelper,
}: Iprops) {
const { t } = useTranslation();
const updateDeviceExpiry = async (optionFn) => {
return updatePublicShareURLHelper({
collectionID: collection.id,
@ -27,7 +29,7 @@ export function ManageLinkExpiry({
};
return (
<Box>
<Typography mb={0.5}>{constants.LINK_EXPIRY}</Typography>
<Typography mb={0.5}>{t('LINK_EXPIRY')}</Typography>
<Select
menuPosition="fixed"
options={shareExpiryOptions}

View file

@ -2,9 +2,9 @@ import { Box, Typography } from '@mui/material';
import { AppContext } from 'pages/_app';
import React, { useContext, useState } from 'react';
import { PublicURL, Collection, UpdatePublicURL } from 'types/collection';
import constants from 'utils/strings/constants';
import { PublicLinkSetPassword } from './setPassword';
import PublicShareSwitch from '../../switch';
import { useTranslation } from 'react-i18next';
interface Iprops {
publicShareProp: PublicURL;
@ -17,6 +17,8 @@ export function ManageLinkPassword({
publicShareProp,
updatePublicShareURLHelper,
}: Iprops) {
const { t } = useTranslation();
const appContext = useContext(AppContext);
const [changePasswordView, setChangePasswordView] = useState(false);
@ -32,11 +34,11 @@ export function ManageLinkPassword({
const confirmDisablePublicUrlPassword = async () => {
appContext.setDialogMessage({
title: constants.DISABLE_PASSWORD,
content: constants.DISABLE_PASSWORD_MESSAGE,
close: { text: constants.CANCEL },
title: t('DISABLE_PASSWORD'),
content: t('DISABLE_PASSWORD_MESSAGE'),
close: { text: t('CANCEL') },
proceed: {
text: constants.DISABLE,
text: t('DISABLE'),
action: () =>
updatePublicShareURLHelper({
collectionID: collection.id,
@ -50,10 +52,7 @@ export function ManageLinkPassword({
return (
<>
<Box>
<Typography mb={0.5}>
{' '}
{constants.LINK_PASSWORD_LOCK}
</Typography>
<Typography mb={0.5}> {t('LINK_PASSWORD_LOCK')}</Typography>
<PublicShareSwitch
checked={!!publicShareProp?.passwordEnabled}
onChange={handlePasswordChangeSetting}

View file

@ -3,8 +3,8 @@ import SingleInputForm, {
SingleInputFormProps,
} from 'components/SingleInputForm';
import React from 'react';
import { useTranslation } from 'react-i18next';
import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker';
import constants from 'utils/strings/constants';
export function PublicLinkSetPassword({
open,
@ -14,6 +14,7 @@ export function PublicLinkSetPassword({
updatePublicShareURLHelper,
setChangePasswordView,
}) {
const { t } = useTranslation();
const savePassword: SingleInputFormProps['callback'] = async (
passphrase,
setFieldError
@ -50,12 +51,12 @@ export function PublicLinkSetPassword({
PaperProps={{ sx: { p: 1 } }}>
<Stack spacing={3} p={1.5}>
<Typography variant="h3" px={1} py={0.5} fontWeight={'bold'}>
{constants.PASSWORD_LOCK}
{t('PASSWORD_LOCK')}
</Typography>
<SingleInputForm
callback={savePassword}
placeholder={constants.RETURN_PASSPHRASE_HINT}
buttonText={constants.LOCK}
placeholder={t('RETURN_PASSPHRASE_HINT')}
buttonText={t('LOCK')}
fieldType="password"
secondaryButtonAction={onClose}
submitButtonProps={{ sx: { mt: 1, mb: 2 } }}

View file

@ -1,7 +1,7 @@
import { Box, Typography } from '@mui/material';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { PublicURL, Collection, UpdatePublicURL } from 'types/collection';
import constants from 'utils/strings/constants';
import PublicShareSwitch from '../switch';
interface Iprops {
@ -15,6 +15,7 @@ export function ManagePublicCollect({
updatePublicShareURLHelper,
collection,
}: Iprops) {
const { t } = useTranslation();
const handleFileDownloadSetting = () => {
updatePublicShareURLHelper({
collectionID: collection.id,
@ -24,7 +25,7 @@ export function ManagePublicCollect({
return (
<Box>
<Typography mb={0.5}>{constants.PUBLIC_COLLECT}</Typography>
<Typography mb={0.5}>{t('PUBLIC_COLLECT')}</Typography>
<PublicShareSwitch
checked={publicShareProp?.enableCollect}
onChange={handleFileDownloadSetting}

View file

@ -2,9 +2,9 @@ import { Box, Typography } from '@mui/material';
import { GalleryContext } from 'pages/gallery';
import { AppContext } from 'pages/_app';
import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { unshareCollection } from 'services/collectionService';
import { Collection } from 'types/collection';
import constants from 'utils/strings/constants';
import ShareeRow from './row';
interface Iprops {
@ -12,6 +12,7 @@ interface Iprops {
}
export function CollectionShareSharees({ collection }: Iprops) {
const { t } = useTranslation();
const appContext = useContext(AppContext);
const galleryContext = useContext(GalleryContext);
@ -32,7 +33,7 @@ export function CollectionShareSharees({ collection }: Iprops) {
return (
<Box mb={3}>
<Typography variant="body2" color="text.secondary">
{constants.SHAREES}
{t('SHAREES')}
</Typography>
{collection.sharees?.map((sharee) => (
<ShareeRow

View file

@ -5,14 +5,15 @@ import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
import OverflowMenu from 'components/OverflowMenu/menu';
import NotInterestedIcon from '@mui/icons-material/NotInterested';
import constants from 'utils/strings/constants';
import { OverflowMenuOption } from 'components/OverflowMenu/option';
import { useTranslation } from 'react-i18next';
interface IProps {
sharee: User;
collectionUnshare: (sharee: User) => void;
}
const ShareeRow = ({ sharee, collectionUnshare }: IProps) => {
const { t } = useTranslation();
const handleClick = () => collectionUnshare(sharee);
return (
<SpaceBetweenFlex>
@ -30,7 +31,7 @@ const ShareeRow = ({ sharee, collectionUnshare }: IProps) => {
color="danger"
onClick={handleClick}
startIcon={<NotInterestedIcon />}>
{constants.REMOVE}
{t('REMOVE')}
</OverflowMenuOption>
</OverflowMenu>
</SpaceBetweenFlex>

View file

@ -6,11 +6,11 @@ import {
Typography,
Button,
Stack,
Link,
} from '@mui/material';
import { AppContext } from 'pages/_app';
import React, { useContext, useEffect, useState } from 'react';
import { preloadImage, initiateEmail } from 'utils/common';
import constants from 'utils/strings/constants';
import VerticallyCentered from './Container';
import DialogTitleWithCloseButton from './DialogBox/TitleWithCloseButton';
import {
@ -21,12 +21,14 @@ import {
import AuthenticateUserModal from './AuthenticateUserModal';
import { logError } from 'utils/sentry';
import { decryptDeleteAccountChallenge } from 'utils/crypto';
import { Trans, useTranslation } from 'react-i18next';
interface Iprops {
onClose: () => void;
open: boolean;
}
const DeleteAccountModal = ({ open, onClose }: Iprops) => {
const { t } = useTranslation();
const { setDialogMessage, isMobile } = useContext(AppContext);
const [authenticateUserModalView, setAuthenticateUserModalView] =
useState(false);
@ -44,13 +46,15 @@ const DeleteAccountModal = ({ open, onClose }: Iprops) => {
const somethingWentWrong = () =>
setDialogMessage({
title: constants.ERROR,
title: t('ERROR'),
close: { variant: 'danger' },
content: constants.UNKNOWN_ERROR,
content: t('UNKNOWN_ERROR'),
});
const initiateDelete = async () => {
try {
askToMailForDeletion();
return;
const deleteChallengeResponse = await getAccountDeleteChallenge();
setDeleteAccountChallenge(
deleteChallengeResponse.encryptedChallenge
@ -68,29 +72,40 @@ const DeleteAccountModal = ({ open, onClose }: Iprops) => {
const confirmAccountDeletion = () => {
setDialogMessage({
title: constants.CONFIRM_ACCOUNT_DELETION_TITLE,
content: constants.CONFIRM_ACCOUNT_DELETION_MESSAGE,
title: t('CONFIRM_ACCOUNT_DELETION_TITLE'),
content: t('CONFIRM_ACCOUNT_DELETION_MESSAGE'),
proceed: {
text: constants.DELETE,
text: t('DELETE'),
action: solveChallengeAndDeleteAccount,
variant: 'danger',
},
close: { text: constants.CANCEL },
close: { text: t('CANCEL') },
});
};
const askToMailForDeletion = () => {
setDialogMessage({
title: constants.DELETE_ACCOUNT,
content: constants.DELETE_ACCOUNT_MESSAGE(),
title: t('DELETE_ACCOUNT'),
content: (
<Trans i18nKey="DELETE_ACCOUNT_MESSAGE">
<p>
Please send an email to
<Link href="mailto:account-deletion@ente.io">
account-deletion@ente.io
</Link>
from your registered email address.
</p>
<p>Your request will be processed within 72 hours.</p>
</Trans>
),
proceed: {
text: constants.DELETE,
text: t('DELETE'),
action: () => {
initiateEmail('account-deletion@ente.io');
},
variant: 'danger',
},
close: { text: constants.CANCEL },
close: { text: t('CANCEL') },
});
};
@ -117,7 +132,7 @@ const DeleteAccountModal = ({ open, onClose }: Iprops) => {
fullScreen={isMobile}>
<DialogTitleWithCloseButton onClose={onClose}>
<Typography variant="h3" fontWeight={'bold'}>
{constants.DELETE_ACCOUNT}
{t('DELETE_ACCOUNT')}
</Typography>
</DialogTitleWithCloseButton>
<DialogContent>
@ -131,7 +146,19 @@ const DeleteAccountModal = ({ open, onClose }: Iprops) => {
</VerticallyCentered>
<Typography color="text.secondary" px={1.5}>
{constants.ASK_FOR_FEEDBACK}
<Trans i18nKey="ASK_FOR_FEEDBACK">
<p>
We'll be sorry to see you go. Are you facing
some issue?
</p>
<p>
Please write to us at{' '}
<Link href="mailto:feedback@ente.io">
feedback@ente.io
</Link>
, maybe there is a way we can help.
</p>
</Trans>
</Typography>
<Stack spacing={1} px={2} sx={{ width: '100%' }}>
@ -140,7 +167,7 @@ const DeleteAccountModal = ({ open, onClose }: Iprops) => {
color="accent"
onClick={sendFeedbackMail}
startIcon={<TickIcon />}>
{constants.SEND_FEEDBACK}
{t('SEND_FEEDBACK')}
</Button>
<Button
size="large"
@ -148,7 +175,7 @@ const DeleteAccountModal = ({ open, onClose }: Iprops) => {
color="danger"
onClick={initiateDelete}
startIcon={<NoAccountsIcon />}>
{constants.DELETE_ACCOUNT}
{t('DELETE_ACCOUNT')}
</Button>
</Stack>
</DialogContent>

View file

@ -1,5 +1,4 @@
import React from 'react';
import constants from 'utils/strings/constants';
import {
Breakpoint,
Button,
@ -14,6 +13,7 @@ import DialogTitleWithCloseButton, {
import DialogBoxBase from './base';
import { DialogBoxAttributes } from 'types/dialogBox';
import DialogIcon from './DialogIcon';
import { useTranslation } from 'react-i18next';
type IProps = React.PropsWithChildren<
Omit<DialogProps, 'onClose' | 'maxSize'> & {
@ -33,6 +33,8 @@ export default function DialogBox({
titleCloseButton,
...props
}: IProps) {
const { t } = useTranslation();
if (!attributes) {
return <></>;
}
@ -81,7 +83,7 @@ export default function DialogBox({
attributes.close?.action();
onClose();
}}>
{attributes.close?.text ?? constants.OK}
{attributes.close?.text ?? t('OK')}
</Button>
)}
{attributes.proceed && (

View file

@ -1,6 +1,5 @@
import React, { useContext } from 'react';
import { Button, Stack, styled, Typography } from '@mui/material';
import constants from 'utils/strings/constants';
import { DeduplicateContext } from 'pages/deduplicate';
import VerticallyCentered, { FlexWrapper } from './Container';
import { Box } from '@mui/material';
@ -8,6 +7,8 @@ import uploadManager from 'services/upload/uploadManager';
import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternateOutlined';
import FolderIcon from '@mui/icons-material/FolderOutlined';
import { UploadTypeSelectorIntent } from 'types/gallery';
import { Trans, useTranslation } from 'react-i18next';
import { EnteLogo } from './EnteLogo';
const Wrapper = styled(Box)`
display: flex;
@ -20,6 +21,7 @@ const NonDraggableImage = styled('img')`
`;
export default function EmptyScreen({ openUploader }) {
const { t } = useTranslation();
const deduplicateContext = useContext(DeduplicateContext);
return deduplicateContext.isOnDeduplicatePage ? (
<VerticallyCentered>
@ -28,7 +30,7 @@ export default function EmptyScreen({ openUploader }) {
color: '#a6a6a6',
fontSize: '18px',
}}>
{constants.NO_DUPLICATES_FOUND}
{t('NO_DUPLICATES_FOUND')}
</div>
</VerticallyCentered>
) : (
@ -40,10 +42,15 @@ export default function EmptyScreen({ openUploader }) {
pb: 1.5,
}}>
<VerticallyCentered sx={{ flex: 'none' }}>
{constants.WELCOME_TO_ENTE()}
<Trans i18nKey={'WELCOME_TO_ENTE'}>
<Typography variant="h3" color="text.secondary" mb={1}>
Welcome to <EnteLogo />
</Typography>
<h2>End to end encrypted photo storage and sharing</h2>
</Trans>
</VerticallyCentered>
<Typography variant="body1" mt={3.5} color="text.secondary">
{constants.WHERE_YOUR_BEST_PHOTOS_LIVE}
{t('WHERE_YOUR_BEST_PHOTOS_LIVE')}
</Typography>
</Stack>
<NonDraggableImage
@ -73,7 +80,7 @@ export default function EmptyScreen({ openUploader }) {
}}>
<FlexWrapper sx={{ gap: 1 }} justifyContent="center">
<AddPhotoAlternateIcon />
{constants.UPLOAD_FIRST_PHOTO}
{t('UPLOAD_FIRST_PHOTO')}
</FlexWrapper>
</Button>
<Button
@ -94,7 +101,7 @@ export default function EmptyScreen({ openUploader }) {
}}>
<FlexWrapper sx={{ gap: 1 }} justifyContent="center">
<FolderIcon />
{constants.IMPORT_YOUR_FOLDERS}
{t('IMPORT_YOUR_FOLDERS')}
</FlexWrapper>
</Button>
</VerticallyCentered>

View file

@ -1,7 +1,7 @@
import { Button, DialogActions, DialogContent, Stack } from '@mui/material';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ExportStats } from 'types/export';
import constants from 'utils/strings/constants';
import { formatDateTime } from 'utils/time/format';
import { FlexWrapper, Label, Value } from './Container';
import { ComfySpan } from './ExportInProgress';
@ -15,20 +15,21 @@ interface Props {
}
export default function ExportFinished(props: Props) {
const { t } = useTranslation();
const totalFiles = props.exportStats.failed + props.exportStats.success;
return (
<>
<DialogContent>
<Stack spacing={2.5}>
<FlexWrapper>
<Label width="40%">{constants.LAST_EXPORT_TIME}</Label>
<Label width="40%">{t('LAST_EXPORT_TIME')}</Label>
<Value width="60%">
{formatDateTime(props.lastExportTime)}
</Value>
</FlexWrapper>
<FlexWrapper>
<Label width="40%">
{constants.SUCCESSFULLY_EXPORTED_FILES}
{t('SUCCESSFULLY_EXPORTED_FILES')}
</Label>
<Value width="60%">
<ComfySpan>
@ -39,7 +40,7 @@ export default function ExportFinished(props: Props) {
{props.exportStats.failed > 0 && (
<FlexWrapper>
<Label width="40%">
{constants.FAILED_EXPORTED_FILES}
{t('FAILED_EXPORTED_FILES')}
</Label>
<Value width="60%">
<ComfySpan>
@ -56,18 +57,18 @@ export default function ExportFinished(props: Props) {
size="large"
color="accent"
onClick={props.retryFailed}>
{constants.RETRY_EXPORT_}
{t('RETRY_EXPORT')}
</Button>
) : (
<Button
size="large"
color="primary"
onClick={props.exportFiles}>
{constants.EXPORT_AGAIN}
{t('EXPORT_AGAIN')}
</Button>
)}
<Button color="secondary" size="large" onClick={props.onHide}>
{constants.CLOSE}
{t('CLOSE')}
</Button>
</DialogActions>
</>

View file

@ -7,10 +7,10 @@ import {
DialogContent,
styled,
} from '@mui/material';
import constants from 'utils/strings/constants';
import { ExportStage } from 'constants/export';
import VerticallyCentered, { FlexWrapper } from './Container';
import { ProgressBar } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
export const ComfySpan = styled('span')`
word-spacing: 1rem;
@ -26,6 +26,7 @@ interface Props {
}
export default function ExportInProgress(props: Props) {
const { t } = useTranslation();
return (
<>
<DialogContent>
@ -64,21 +65,21 @@ export default function ExportInProgress(props: Props) {
size="large"
onClick={props.resumeExport}
color="accent">
{constants.RESUME}
{t('RESUME')}
</Button>
) : (
<Button
size="large"
onClick={props.pauseExport}
color="primary">
{constants.PAUSE}
{t('PAUSE')}
</Button>
)}
<Button
size="large"
onClick={props.cancelExport}
color="secondary">
{constants.CANCEL}
{t('CANCEL')}
</Button>
</DialogActions>
</>

View file

@ -1,16 +1,18 @@
import { Button, DialogActions, DialogContent } from '@mui/material';
import React from 'react';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
interface Props {
startExport: () => void;
}
export default function ExportInit({ startExport }: Props) {
const { t } = useTranslation();
return (
<DialogContent>
<DialogActions>
<Button size="large" color="accent" onClick={startExport}>
{constants.START}
{t('START')}
</Button>
</DialogActions>
</DialogContent>

View file

@ -17,7 +17,6 @@ import { sleep } from 'utils/common';
import { getExportRecordFileUID } from 'utils/export';
import { logError } from 'utils/sentry';
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
import constants from 'utils/strings/constants';
import { FlexWrapper, Label, Value } from './Container';
import ExportFinished from './ExportFinished';
import ExportInit from './ExportInit';
@ -35,6 +34,7 @@ import { getLocalUserDetails } from 'utils/user';
import { AppContext } from 'pages/_app';
import { getExportDirectoryDoesNotExistMessage } from 'utils/ui';
import { addLogLine } from 'utils/logging';
import { useTranslation } from 'react-i18next';
const ExportFolderPathContainer = styled('span')`
white-space: nowrap;
@ -52,6 +52,7 @@ interface Props {
onHide: () => void;
}
export default function ExportModal(props: Props) {
const { t } = useTranslation();
const appContext = useContext(AppContext);
const userDetails = useMemo(() => getLocalUserDetails(), []);
const [exportStage, setExportStage] = useState(ExportStage.INIT);
@ -384,7 +385,7 @@ export default function ExportModal(props: Props) {
return (
<Dialog open={props.show} onClose={props.onHide} maxWidth="xs">
<DialogTitleWithCloseButton onClose={props.onHide}>
{constants.EXPORT_DATA}
{t('EXPORT_DATA')}
</DialogTitleWithCloseButton>
<DialogContent>
<Stack spacing={2}>
@ -403,13 +404,15 @@ export default function ExportModal(props: Props) {
}
function ExportDirectory({ exportFolder, selectExportDirectory, exportStage }) {
const { t } = useTranslation();
return (
<FlexWrapper>
<Label width="30%">{constants.DESTINATION}</Label>
<Label width="30%">{t('DESTINATION')}</Label>
<Value width="70%">
{!exportFolder ? (
<Button color={'accent'} onClick={selectExportDirectory}>
{constants.SELECT_FOLDER}
{t('SELECT_FOLDER')}
</Button>
) : (
<>
@ -432,9 +435,11 @@ function ExportDirectory({ exportFolder, selectExportDirectory, exportStage }) {
}
function ExportSize({ exportSize }) {
const { t } = useTranslation();
return (
<FlexWrapper>
<Label width="30%">{constants.EXPORT_SIZE} </Label>
<Label width="30%">{t('EXPORT_SIZE')} </Label>
<Value width="70%">
{exportSize ? `${exportSize}` : <EnteSpinner />}
</Value>
@ -443,6 +448,7 @@ function ExportSize({ exportSize }) {
}
function ExportDirectoryOption({ selectExportDirectory }) {
const { t } = useTranslation();
const handleClick = () => {
try {
selectExportDirectory();
@ -464,7 +470,7 @@ function ExportDirectoryOption({ selectExportDirectory }) {
<OverflowMenuOption
onClick={handleClick}
startIcon={<FolderIcon />}>
{constants.CHANGE_FOLDER}
{t('CHANGE_FOLDER')}
</OverflowMenuOption>
</OverflowMenu>
);

View file

@ -1,13 +1,15 @@
import React from 'react';
import { Button } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { FIX_STATE } from '.';
import constants from 'utils/strings/constants';
export default function FixCreationTimeFooter({
fixState,
startFix,
...props
}) {
const { t } = useTranslation();
return (
fixState !== FIX_STATE.RUNNING && (
<div
@ -25,7 +27,7 @@ export default function FixCreationTimeFooter({
onClick={() => {
props.hide();
}}>
{constants.CANCEL}
{t('CANCEL')}
</Button>
)}
{fixState === FIX_STATE.COMPLETED && (
@ -33,7 +35,7 @@ export default function FixCreationTimeFooter({
block
variant={'outline-secondary'}
onClick={props.hide}>
{constants.CLOSE}
{t('CLOSE')}
</Button>
)}
{(fixState === FIX_STATE.NOT_STARTED ||
@ -45,7 +47,7 @@ export default function FixCreationTimeFooter({
block
variant={'outline-success'}
onClick={startFix}>
{constants.FIX_CREATION_TIME}
{t('FIX_CREATION_TIME')}
</Button>
</>
)}

View file

@ -1,4 +1,3 @@
import constants from 'utils/strings/constants';
import DialogBox from '../DialogBox';
import React, { useContext, useEffect, useState } from 'react';
import { updateCreationTimeWithExif } from 'services/updateCreationTimeWithExif';
@ -9,6 +8,8 @@ import FixCreationTimeFooter from './footer';
import { Formik } from 'formik';
import FixCreationTimeOptions from './options';
import { useTranslation } from 'react-i18next';
import { TFunction } from 'i18next';
export interface FixCreationTimeAttributes {
files: EnteFile[];
}
@ -37,22 +38,30 @@ interface formValues {
customTime: Date;
}
function Message(props: { fixState: FIX_STATE }) {
function Message({
fixState,
t,
}: {
t: TFunction<'translations', undefined, 'translations'>;
fixState: FIX_STATE;
}) {
let message = null;
switch (props.fixState) {
switch (fixState) {
case FIX_STATE.NOT_STARTED:
message = constants.UPDATE_CREATION_TIME_NOT_STARTED();
message = t('UPDATE_CREATION_TIME_NOT_STARTED');
break;
case FIX_STATE.COMPLETED:
message = constants.UPDATE_CREATION_TIME_COMPLETED();
message = t('UPDATE_CREATION_TIME_COMPLETED');
break;
case FIX_STATE.COMPLETED_WITH_ERRORS:
message = constants.UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR();
message = t('UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR');
break;
}
return message ? <div>{message}</div> : <></>;
}
export default function FixCreationTime(props: Props) {
const { t } = useTranslation();
const [fixState, setFixState] = useState(FIX_STATE.NOT_STARTED);
const [progressTracker, setProgressTracker] = useState({
current: 0,
@ -99,8 +108,8 @@ export default function FixCreationTime(props: Props) {
attributes={{
title:
fixState === FIX_STATE.RUNNING
? constants.FIX_CREATION_TIME_IN_PROGRESS
: constants.FIX_CREATION_TIME,
? t('FIX_CREATION_TIME_IN_PROGRESS')
: t('FIX_CREATION_TIME'),
nonClosable: true,
}}>
<div
@ -112,7 +121,7 @@ export default function FixCreationTime(props: Props) {
? { alignItems: 'center' }
: {}),
}}>
<Message fixState={fixState} />
<Message fixState={fixState} t={t} />
{fixState === FIX_STATE.RUNNING && (
<FixCreationTimeRunning progressTracker={progressTracker} />

View file

@ -3,7 +3,7 @@ import { FIX_OPTIONS } from '.';
import { Form } from 'react-bootstrap';
import EnteDateTimePicker from 'components/EnteDateTimePicker';
import { Row, Value } from 'components/Container';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
const Option = ({
value,
@ -38,13 +38,14 @@ const Option = ({
);
export default function FixCreationTimeOptions({ handleChange, values }) {
const { t } = useTranslation();
return (
<Form noValidate>
<Row style={{ margin: '0' }}>
<Option
value={FIX_OPTIONS.DATE_TIME_ORIGINAL}
onChange={handleChange('option')}
label={constants.DATE_TIME_ORIGINAL}
label={t('DATE_TIME_ORIGINAL')}
selected={Number(values.option)}
/>
</Row>
@ -52,7 +53,7 @@ export default function FixCreationTimeOptions({ handleChange, values }) {
<Option
value={FIX_OPTIONS.DATE_TIME_DIGITIZED}
onChange={handleChange('option')}
label={constants.DATE_TIME_DIGITIZED}
label={t('DATE_TIME_DIGITIZED')}
selected={Number(values.option)}
/>
</Row>
@ -61,7 +62,7 @@ export default function FixCreationTimeOptions({ handleChange, values }) {
<Option
value={FIX_OPTIONS.CUSTOM_TIME}
onChange={handleChange('option')}
label={constants.CUSTOM_TIME}
label={t('CUSTOM_TIME')}
selected={Number(values.option)}
/>
</Value>

View file

@ -1,9 +1,10 @@
import constants from 'utils/strings/constants';
import { ComfySpan } from 'components/ExportInProgress';
import React from 'react';
import { ProgressBar } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
export default function FixCreationTimeRunning({ progressTracker }) {
const { t } = useTranslation();
return (
<>
<div style={{ marginBottom: '10px' }}>
@ -13,7 +14,7 @@ export default function FixCreationTimeRunning({ progressTracker }) {
</ComfySpan>{' '}
<span style={{ marginLeft: '10px' }}>
{' '}
{constants.CREATION_TIME_UPDATED}
{t('CREATION_TIME_UPDATED')}
</span>
</div>
<div

View file

@ -1,4 +1,3 @@
import constants from 'utils/strings/constants';
import DialogBox from './DialogBox';
import React, { useEffect, useState } from 'react';
import { ProgressBar, Button } from 'react-bootstrap';
@ -9,6 +8,8 @@ import {
} from 'services/migrateThumbnailService';
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
import { logError } from 'utils/sentry';
import { useTranslation } from 'react-i18next';
import { TFunction } from 'i18next';
export type SetProgressTracker = React.Dispatch<
React.SetStateAction<{
@ -29,21 +30,27 @@ export enum FIX_STATE {
COMPLETED,
COMPLETED_WITH_ERRORS,
}
function Message(props: { fixState: FIX_STATE }) {
function Message({
fixState,
t,
}: {
t: TFunction<'translation', undefined, 'translation'>;
fixState: FIX_STATE;
}) {
let message = null;
switch (props.fixState) {
switch (fixState) {
case FIX_STATE.NOT_STARTED:
case FIX_STATE.FIX_LATER:
message = constants.REPLACE_THUMBNAIL_NOT_STARTED();
message = t('REPLACE_THUMBNAIL_NOT_STARTED');
break;
case FIX_STATE.COMPLETED:
message = constants.REPLACE_THUMBNAIL_COMPLETED();
message = t('REPLACE_THUMBNAIL_COMPLETED');
break;
case FIX_STATE.NOOP:
message = constants.REPLACE_THUMBNAIL_NOOP();
message = t('REPLACE_THUMBNAIL_NOOP');
break;
case FIX_STATE.COMPLETED_WITH_ERRORS:
message = constants.REPLACE_THUMBNAIL_COMPLETED_WITH_ERROR();
message = t('REPLACE_THUMBNAIL_COMPLETED_WITH_ERROR');
break;
}
return message ? (
@ -53,6 +60,7 @@ function Message(props: { fixState: FIX_STATE }) {
);
}
export default function FixLargeThumbnails(props: Props) {
const { t } = useTranslation();
const [fixState, setFixState] = useState(FIX_STATE.NOT_STARTED);
const [progressTracker, setProgressTracker] = useState({
current: 0,
@ -140,7 +148,7 @@ export default function FixLargeThumbnails(props: Props) {
open={props.isOpen}
onClose={props.hide}
attributes={{
title: constants.COMPRESS_THUMBNAILS,
title: t('COMPRESS_THUMBNAILS'),
}}>
<div
style={{
@ -150,7 +158,7 @@ export default function FixLargeThumbnails(props: Props) {
alignItems: 'center',
flexDirection: 'column',
}}>
<Message fixState={fixState} />
<Message t={t} fixState={fixState} />
{fixState === FIX_STATE.RUNNING && (
<>
@ -162,7 +170,7 @@ export default function FixLargeThumbnails(props: Props) {
</ComfySpan>{' '}
<span style={{ marginLeft: '10px' }}>
{' '}
{constants.THUMBNAIL_REPLACED}
{t('THUMBNAIL_REPLACED')}
</span>
</div>
<div
@ -197,14 +205,14 @@ export default function FixLargeThumbnails(props: Props) {
updateFixState(FIX_STATE.FIX_LATER);
props.hide();
}}>
{constants.FIX_THUMBNAIL_LATER}
{t('FIX_THUMBNAIL_LATER')}
</Button>
) : (
<Button
block
variant={'outline-secondary'}
onClick={props.hide}>
{constants.CLOSE}
{t('CLOSE')}
</Button>
)}
{(fixState === FIX_STATE.NOT_STARTED ||
@ -217,7 +225,7 @@ export default function FixLargeThumbnails(props: Props) {
block
variant={'outline-success'}
onClick={() => startFix()}>
{constants.FIX_THUMBNAIL}
{t('FIX_THUMBNAIL')}
</Button>
</>
)}

View file

@ -1,8 +1,8 @@
import React, { useEffect, useState, useContext } from 'react';
import { styled } from '@mui/material';
import constants from 'utils/strings/constants';
import CloseIcon from '@mui/icons-material/Close';
import { AppContext } from 'pages/_app';
import { useTranslation } from 'react-i18next';
const CloseButtonWrapper = styled('div')`
position: absolute;
@ -42,6 +42,7 @@ type Props = React.PropsWithChildren<{
}>;
export default function FullScreenDropZone(props: Props) {
const { t } = useTranslation();
const appContext = useContext(AppContext);
const [isDragActive, setIsDragActive] = useState(false);
@ -87,8 +88,8 @@ export default function FullScreenDropZone(props: Props) {
<CloseIcon />
</CloseButtonWrapper>
{appContext.watchFolderView
? constants.WATCH_FOLDER_DROPZONE_MESSAGE
: constants.UPLOAD_DROPZONE_MESSAGE}
? t('WATCH_FOLDER_DROPZONE_MESSAGE')
: t('UPLOAD_DROPZONE_MESSAGE')}
</Overlay>
)}
{props.children}

View file

@ -1,4 +1,3 @@
import constants from 'utils/strings/constants';
import React, { useEffect } from 'react';
import { useRouter } from 'next/router';
import { sendOtt } from 'services/userService';
@ -9,12 +8,14 @@ import FormPaperFooter from './Form/FormPaper/Footer';
import LinkButton from './pages/gallery/LinkButton';
import SingleInputForm, { SingleInputFormProps } from './SingleInputForm';
import { Input } from '@mui/material';
import { useTranslation } from 'react-i18next';
interface LoginProps {
signUp: () => void;
}
export default function Login(props: LoginProps) {
const { t } = useTranslation();
const router = useRouter();
useEffect(() => {
@ -37,25 +38,25 @@ export default function Login(props: LoginProps) {
setData(LS_KEYS.USER, { email });
router.push(PAGES.VERIFY);
} catch (e) {
setFieldError(`${constants.UNKNOWN_ERROR} ${e.message}`);
setFieldError(`${t('UNKNOWN_ERROR} ${e.message}')}`);
}
};
return (
<>
<FormPaperTitle>{constants.LOGIN}</FormPaperTitle>
<FormPaperTitle>{t('LOGIN')}</FormPaperTitle>
<SingleInputForm
callback={loginUser}
fieldType="email"
placeholder={constants.ENTER_EMAIL}
buttonText={constants.LOGIN}
placeholder={t('ENTER_EMAIL')}
buttonText={t('LOGIN')}
autoComplete="username"
hiddenPostInput={<Input hidden type="password" value="" />}
/>
<FormPaperFooter>
<LinkButton onClick={props.signUp}>
{constants.NO_ACCOUNT}
{t('NO_ACCOUNT')}
</LinkButton>
</FormPaperFooter>
</>

View file

@ -1,23 +1,25 @@
import {
Stack,
Box,
Button,
FormGroup,
Checkbox,
FormControlLabel,
DialogProps,
Typography,
Link,
} from '@mui/material';
import { EnteDrawer } from 'components/EnteDrawer';
import Titlebar from 'components/Titlebar';
import { FACE_SEARCH_PRIVACY_POLICY_LINK } from 'constants/urls';
import { useEffect, useState } from 'react';
import constants from 'utils/strings/constants';
import { Trans, useTranslation } from 'react-i18next';
export default function EnableFaceSearch({
open,
onClose,
enableFaceSearch,
onRootClose,
}) {
const { t } = useTranslation();
const [acceptTerms, setAcceptTerms] = useState(false);
useEffect(() => {
@ -47,13 +49,31 @@ export default function EnableFaceSearch({
<Stack spacing={'4px'} py={'12px'}>
<Titlebar
onClose={onClose}
title={constants.ENABLE_FACE_SEARCH_TITLE}
title={t('ENABLE_FACE_SEARCH_TITLE')}
onRootClose={handleRootClose}
/>
<Stack py={'20px'} px={'8px'} spacing={'32px'}>
<Box px={'8px'}>
{constants.ENABLE_FACE_SEARCH_DESCRIPTION()}
</Box>
<Typography color="text.secondary" px={'8px'}>
<Trans i18nKey={'ENABLE_FACE_SEARCH_DESCRIPTION'}>
If you enable face search, ente will extract face
geometry from your photos. This will happen on your
device, and any generated biometric data will be
end-to-encrypted.
<br />
<br />
<Link
target={'_blank'}
href={FACE_SEARCH_PRIVACY_POLICY_LINK}
underline="always"
sx={{
color: 'inherit',
textDecorationColor: 'inherit',
}}>
Please click here for more details about this
feature in our privacy policy
</Link>
</Trans>
</Typography>
<FormGroup sx={{ width: '100%' }}>
<FormControlLabel
sx={{
@ -70,7 +90,7 @@ export default function EnableFaceSearch({
}
/>
}
label={constants.FACE_SEARCH_CONFIRMATION}
label={t('FACE_SEARCH_CONFIRMATION')}
/>
</FormGroup>
<Stack px={'8px'} spacing={'8px'}>
@ -79,13 +99,13 @@ export default function EnableFaceSearch({
size="large"
disabled={!acceptTerms}
onClick={enableFaceSearch}>
{constants.ENABLE_FACE_SEARCH}
{t('ENABLE_FACE_SEARCH')}
</Button>
<Button
color={'secondary'}
size="large"
onClick={onClose}>
{constants.CANCEL}
{t('CANCEL')}
</Button>
</Stack>
</Stack>

View file

@ -1,35 +1,56 @@
import { Stack, Box, Button } from '@mui/material';
import { Stack, Box, Button, Typography } from '@mui/material';
import Titlebar from 'components/Titlebar';
import { ML_BLOG_LINK } from 'constants/urls';
import { Trans, useTranslation } from 'react-i18next';
import { openLink } from 'utils/common';
import constants from 'utils/strings/constants';
export default function EnableMLSearch({
onClose,
enableMlSearch,
onRootClose,
}) {
const { t } = useTranslation();
return (
<Stack spacing={'4px'} py={'12px'}>
<Titlebar
onClose={onClose}
title={constants.ML_SEARCH}
title={t('ML_SEARCH')}
onRootClose={onRootClose}
/>
<Stack py={'20px'} px={'8px'} spacing={'32px'}>
<Box px={'8px'}>{constants.ML_SEARCH_DESCRIPTION()}</Box>
<Box px={'8px'}>
{' '}
<Typography color="text.secondary">
<Trans i18nKey={'ML_SEARCH_DESCRIPTION'}>
This will enable on-device machine learning and face
search which will start analyzing your uploaded
photos locally.
<br />
<br />
For the first run after login or enabling this
feature, it will download all images on local device
to analyze them. So please only enable this if you
are ok with bandwidth and local processing of all
images in your photo library.
<br />
<br />
If this is the first time you're enabling this,
we'll also ask your permission to process face data.
</Trans>
</Typography>
</Box>
<Stack px={'8px'} spacing={'8px'}>
<Button
color={'accent'}
size="large"
onClick={enableMlSearch}>
{constants.ENABLE}
{t('ENABLE')}
</Button>
<Button
color={'secondary'}
size="large"
onClick={() => openLink(ML_BLOG_LINK, true)}>
{constants.ML_MORE_DETAILS}
{t('ML_MORE_DETAILS')}
</Button>
</Stack>
</Stack>

View file

@ -1,18 +1,19 @@
import { Box, DialogProps } from '@mui/material';
import { Box, DialogProps, Typography } from '@mui/material';
import { EnteDrawer } from 'components/EnteDrawer';
import { AppContext } from 'pages/_app';
import { useContext, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import {
getFaceSearchEnabledStatus,
updateFaceSearchEnabledStatus,
} from 'services/userService';
import { logError } from 'utils/sentry';
import constants from 'utils/strings/constants';
import EnableFaceSearch from './enableFaceSearch';
import EnableMLSearch from './enableMLSearch';
import ManageMLSearch from './manageMLSearch';
const MLSearchSettings = ({ open, onClose, onRootClose }) => {
const { t } = useTranslation();
const {
updateMlSearchEnabled,
mlSearchEnabled,
@ -82,12 +83,23 @@ const MLSearchSettings = ({ open, onClose, onRootClose }) => {
const confirmDisableFaceSearch = () => {
setDialogMessage({
title: constants.DISABLE_FACE_SEARCH_TITLE,
content: constants.DISABLE_FACE_SEARCH_DESCRIPTION(),
close: { text: constants.CANCEL },
title: t('DISABLE_FACE_SEARCH_TITLE'),
content: (
<Typography>
<Trans>
ente will stop processing face geometry, and will also
disable ML search (beta)
<br />
<br />
You can reenable face search again if you wish, so this
operation is safe
</Trans>
</Typography>
),
close: { text: t('CANCEL') },
proceed: {
variant: 'primary',
text: constants.DISABLE_FACE_SEARCH,
text: t('DISABLE_FACE_SEARCH'),
action: disableFaceSearch,
},
});

View file

@ -1,7 +1,7 @@
import { Stack, Box, ButtonProps, TypographyVariant } from '@mui/material';
import SidebarButton from 'components/Sidebar/Button';
import Titlebar from 'components/Titlebar';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
type Iprops = ButtonProps<'button', { typographyVariant?: TypographyVariant }>;
@ -20,20 +20,21 @@ export default function ManageMLSearch({
handleDisableFaceSearch,
onRootClose,
}) {
const { t } = useTranslation();
return (
<Stack spacing={'4px'} py={'12px'}>
<Titlebar
onClose={onClose}
title={constants.ML_SEARCH}
title={t('ML_SEARCH')}
onRootClose={onRootClose}
/>
<Box px={'16px'}>
<Stack py={'20px'} spacing={'24px'}>
<ManageOptions onClick={disableMlSearch}>
{constants.DISABLE_BETA}
{t('DISABLE_BETA')}
</ManageOptions>
<ManageOptions onClick={handleDisableFaceSearch}>
{constants.DISABLE_FACE_SEARCH}
{t('DISABLE_FACE_SEARCH')}
</ManageOptions>
</Stack>
</Box>

View file

@ -2,14 +2,15 @@ import Box from '@mui/material/Box';
import { Chip } from 'components/Chip';
import { Legend } from 'components/PhotoViewer/styledComponents/Legend';
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { EnteFile } from 'types/file';
import mlIDbStorage from 'utils/storage/mlIDbStorage';
import constants from 'utils/strings/constants';
export function ObjectLabelList(props: {
file: EnteFile;
updateMLDataIndex: number;
}) {
const { t } = useTranslation();
const [objects, setObjects] = useState<Array<string>>([]);
useEffect(() => {
let didCancel = false;
@ -34,9 +35,7 @@ export function ObjectLabelList(props: {
return (
<div>
<Legend sx={{ pb: 1, display: 'block' }}>
{constants.OBJECTS}
</Legend>
<Legend sx={{ pb: 1, display: 'block' }}>{t('OBJECTS')}</Legend>
<Box
display={'flex'}
gap={1}

View file

@ -10,9 +10,9 @@ import { EnteFile } from 'types/file';
import { ImageCacheView } from './ImageViews';
import { CACHES } from 'constants/cache';
import { Legend } from 'components/PhotoViewer/styledComponents/Legend';
import constants from 'utils/strings/constants';
import { addLogLine } from 'utils/logging';
import { logError } from 'utils/sentry';
import { useTranslation } from 'react-i18next';
const FaceChipContainer = styled.div`
display: flex;
@ -78,6 +78,7 @@ export interface PhotoPeopleListProps extends PeopleListPropsBase {
}
export function PhotoPeopleList(props: PhotoPeopleListProps) {
const { t } = useTranslation();
const [people, setPeople] = useState<Array<Person>>([]);
useEffect(() => {
@ -103,7 +104,7 @@ export function PhotoPeopleList(props: PhotoPeopleListProps) {
return (
<div>
<Legend>{constants.PEOPLE}</Legend>
<Legend>{t('PEOPLE')}</Legend>
<PeopleList people={people} onSelect={props.onSelect}></PeopleList>
</div>
);
@ -143,6 +144,7 @@ export function UnidentifiedFaces(props: {
file: EnteFile;
updateMLDataIndex: number;
}) {
const { t } = useTranslation();
const [faces, setFaces] = useState<Array<Face>>([]);
useEffect(() => {
@ -165,7 +167,7 @@ export function UnidentifiedFaces(props: {
return (
<>
<div>
<Legend>{constants.UNIDENTIFIED_FACES}</Legend>
<Legend>{t('UNIDENTIFIED_FACES')}</Legend>
</div>
<FaceChipContainer>
{faces &&

View file

@ -5,10 +5,11 @@ import React, { useContext, useEffect } from 'react';
import billingService from 'services/billingService';
import { getFamilyPlanAdmin } from 'utils/user/family';
import { preloadImage } from 'utils/common';
import constants from 'utils/strings/constants';
import DialogTitleWithCloseButton from './DialogBox/TitleWithCloseButton';
import { useTranslation } from 'react-i18next';
export function MemberSubscriptionManage({ open, userDetails, onClose }) {
const { t } = useTranslation();
const { setDialogMessage, isMobile } = useContext(AppContext);
useEffect(() => {
@ -20,23 +21,23 @@ export function MemberSubscriptionManage({ open, userDetails, onClose }) {
await billingService.leaveFamily();
} catch (e) {
setDialogMessage({
title: constants.ERROR,
title: t('ERROR'),
close: { variant: 'danger' },
content: constants.UNKNOWN_ERROR,
content: t('UNKNOWN_ERROR'),
});
}
}
const confirmLeaveFamily = () =>
setDialogMessage({
title: `${constants.LEAVE_FAMILY_PLAN}`,
content: constants.LEAVE_FAMILY_CONFIRM,
title: t('LEAVE_FAMILY_PLAN}'),
content: t('LEAVE_FAMILY_CONFIRM'),
proceed: {
text: constants.LEAVE,
text: t('LEAVE'),
action: onLeaveFamilyClick,
variant: 'danger',
},
close: {
text: constants.CANCEL,
text: t('CANCEL'),
},
});
@ -53,17 +54,17 @@ export function MemberSubscriptionManage({ open, userDetails, onClose }) {
fullScreen={isMobile}>
<DialogTitleWithCloseButton onClose={onClose}>
<Typography variant="h3" fontWeight={'bold'}>
{constants.SUBSCRIPTION}
{t('SUBSCRIPTION')}
</Typography>
<Typography color={'text.secondary'}>
{constants.FAMILY_PLAN}
{t('FAMILY_PLAN')}
</Typography>
</DialogTitleWithCloseButton>
<DialogContent>
<VerticallyCentered>
<Box mb={4}>
<Typography color="text.secondary">
{constants.FAMILY_SUBSCRIPTION_INFO}
{t('FAMILY_SUBSCRIPTION_INFO')}
</Typography>
<Typography>
{getFamilyPlanAdmin(userDetails.familyData)?.email}
@ -82,7 +83,7 @@ export function MemberSubscriptionManage({ open, userDetails, onClose }) {
variant="outlined"
color="danger"
onClick={confirmLeaveFamily}>
{constants.LEAVE_FAMILY_PLAN}
{t('LEAVE_FAMILY_PLAN')}
</Button>
</FlexWrapper>
</VerticallyCentered>

View file

@ -1,8 +1,8 @@
import { Typography } from '@mui/material';
import { PasswordStrength } from 'constants/crypto';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { estimatePasswordStrength } from 'utils/crypto';
import constants from 'utils/strings/constants';
import { FlexWrapper } from './Container';
export const PasswordStrengthHint = ({
@ -10,6 +10,7 @@ export const PasswordStrengthHint = ({
}: {
password: string;
}): JSX.Element => {
const { t } = useTranslation();
const passwordStrength = useMemo(
() => estimatePasswordStrength(password),
[password]
@ -28,9 +29,7 @@ export const PasswordStrengthHint = ({
})}
textAlign={'left'}
flex={1}>
{password
? constants.PASSPHRASE_STRENGTH(passwordStrength)
: ''}
{password ? t('PASSPHRASE_STRENGTH', { passwordStrength }) : ''}
</Typography>
</FlexWrapper>
);

View file

@ -12,7 +12,6 @@ import {
SPACE_BTW_DATES_TO_IMAGE_CONTAINER_WIDTH_RATIO,
IMAGE_CONTAINER_MAX_WIDTH,
} from 'constants/gallery';
import constants from 'utils/strings/constants';
import { PublicCollectionGalleryContext } from 'utils/publicCollectionGallery';
import { ENTE_WEBSITE_LINK } from 'constants/urls';
import { convertBytesToHumanReadable } from 'utils/file/size';
@ -22,6 +21,7 @@ import { Typography } from '@mui/material';
import { GalleryContext } from 'pages/gallery';
import { SpecialPadding } from 'styles/SpecialPadding';
import { formatDate } from 'utils/time/format';
import { Trans, useTranslation } from 'react-i18next';
const A_DAY = 24 * 60 * 60 * 1000;
const FOOTER_HEIGHT = 90;
@ -178,6 +178,7 @@ export function PhotoList({
getThumbnail,
activeCollection,
}: Props) {
const { t } = useTranslation();
const galleryContext = useContext(GalleryContext);
const publicCollectionGalleryContext = useContext(
PublicCollectionGalleryContext
@ -229,14 +230,14 @@ export function PhotoList({
skipMerge = true;
groupByFileSize(timeStampList);
} else {
groupByTime(timeStampList);
groupByTime(t, timeStampList);
}
if (!skipMerge) {
timeStampList = mergeTimeStampList(timeStampList, columns);
}
if (timeStampList.length === 1) {
timeStampList.push(getEmptyListItem());
timeStampList.push(getEmptyListItem(t));
}
timeStampList.push(getVacuumItem(timeStampList));
if (publicCollectionGalleryContext.accessedThroughSharedURL) {
@ -247,7 +248,7 @@ export function PhotoList({
)
);
}
timeStampList.push(getAlbumsFooter());
timeStampList.push(getAlbumsFooter(t));
} else if (showAppDownloadBanner) {
timeStampList.push(getAppDownloadFooter());
}
@ -316,7 +317,7 @@ export function PhotoList({
getPhotoListFooter(
publicCollectionGalleryContext.photoListFooter
),
getAlbumsFooter(),
getAlbumsFooter(t),
];
}
} else if (showAppDownloadBanner) {
@ -375,7 +376,7 @@ export function PhotoList({
}
};
const groupByTime = (timeStampList: TimeStampListItem[]) => {
const groupByTime = (t, timeStampList: TimeStampListItem[]) => {
let listItemIndex = 0;
let currentDate;
filteredData.forEach((item, index) => {
@ -391,12 +392,12 @@ export function PhotoList({
timeStampList.push({
itemType: ITEM_TYPE.TIME,
date: isSameDay(new Date(currentDate), new Date())
? constants.TODAY
? t('TODAY')
: isSameDay(
new Date(currentDate),
new Date(Date.now() - A_DAY)
)
? constants.YESTERDAY
? t('YESTERDAY')
: formatDate(currentDate),
id: currentDate.toString(),
});
@ -450,12 +451,12 @@ export function PhotoList({
};
};
const getEmptyListItem = () => {
const getEmptyListItem = (t) => {
return {
itemType: ITEM_TYPE.OTHER,
item: (
<NothingContainer span={columns}>
<div>{constants.NOTHING_HERE}</div>
<div>{t('NOTHING_HERE')}</div>
</NothingContainer>
),
id: 'empty-list-banner',
@ -493,23 +494,41 @@ export function PhotoList({
item: (
<FooterContainer span={columns}>
<Typography variant="body2">
{constants.INSTALL_MOBILE_APP()}
<Trans>
Install our{' '}
<a
href="https://play.google.com/store/apps/details?id=io.ente.photos"
target="_blank"
style={{ color: '#51cd7c' }}
rel="noreferrer">
Android
</a>{' '}
or{' '}
<a
href="https://apps.apple.com/in/app/ente-photos/id1542026904"
style={{ color: '#51cd7c' }}
target="_blank"
rel="noreferrer">
iOS app{' '}
</a>
to automatically backup all your photos
</Trans>
</Typography>
</FooterContainer>
),
};
};
const getAlbumsFooter = () => {
const getAlbumsFooter = (t) => {
return {
itemType: ITEM_TYPE.MARKETING_FOOTER,
height: ALBUM_FOOTER_HEIGHT,
item: (
<AlbumFooterContainer span={columns}>
<Typography variant="body2">
{constants.SHARED_USING}{' '}
{t('SHARED_USING')}{' '}
<Link target="_blank" href={ENTE_WEBSITE_LINK}>
{constants.ENTE_IO}
{t('ENTE_IO')}
</Link>
</Typography>
</AlbumFooterContainer>
@ -624,6 +643,7 @@ export function PhotoList({
};
const renderListItem = (
t,
listItem: TimeStampListItem,
isScrolling: boolean
) => {
@ -646,9 +666,9 @@ export function PhotoList({
case ITEM_TYPE.SIZE_AND_COUNT:
return (
<SizeAndCountContainer span={columns}>
{listItem.fileCount} {constants.FILES},{' '}
{listItem.fileCount} {t('FILES')},{' '}
{convertBytesToHumanReadable(listItem.fileSize || 0)}{' '}
{constants.EACH}
{t('EACH')}
</SizeAndCountContainer>
);
case ITEM_TYPE.FILE: {
@ -699,7 +719,7 @@ export function PhotoList({
columns={columns}
shrinkRatio={shrinkRatio}
groups={timeStampList[index].groups}>
{renderListItem(timeStampList[index], isScrolling)}
{renderListItem(t, timeStampList[index], isScrolling)}
</ListContainer>
</ListItem>
)}

View file

@ -1,5 +1,4 @@
import React from 'react';
import constants from 'utils/strings/constants';
import { Stack, styled, Typography } from '@mui/material';
import { FileInfoSidebar } from '.';
@ -7,6 +6,7 @@ import Titlebar from 'components/Titlebar';
import { Box } from '@mui/system';
import CopyButton from 'components/CodeBlock/CopyButton';
import { formatDateFull } from 'utils/time/format';
import { useTranslation } from 'react-i18next';
const ExifItem = styled(Box)`
padding-left: 8px;
@ -39,6 +39,8 @@ export function ExifData(props: {
filename: string;
onInfoClose: () => void;
}) {
const { t } = useTranslation();
const { exif, open, onClose, filename, onInfoClose } = props;
if (!exif) {
@ -53,7 +55,7 @@ export function ExifData(props: {
<FileInfoSidebar open={open} onClose={onClose}>
<Titlebar
onClose={onClose}
title={constants.EXIF}
title={t('EXIF')}
caption={filename}
onRootClose={handleRootClose}
actionButton={

View file

@ -1,10 +1,10 @@
import React from 'react';
import constants from 'utils/strings/constants';
import { DialogContent, DialogTitle } from '@mui/material';
import DialogBoxBase from 'components/DialogBox/base';
import SingleInputForm, {
SingleInputFormProps,
} from 'components/SingleInputForm';
import { useTranslation } from 'react-i18next';
export const FileNameEditDialog = ({
isInEditMode,
@ -13,6 +13,8 @@ export const FileNameEditDialog = ({
extension,
saveEdits,
}) => {
const { t } = useTranslation();
const onSubmit: SingleInputFormProps['callback'] = async (
filename,
setFieldError
@ -21,7 +23,7 @@ export const FileNameEditDialog = ({
await saveEdits(filename);
closeEditMode();
} catch (e) {
setFieldError(constants.UNKNOWN_ERROR);
setFieldError(t('UNKNOWN_ERROR'));
}
};
return (
@ -29,13 +31,13 @@ export const FileNameEditDialog = ({
open={isInEditMode}
onClose={closeEditMode}
sx={{ zIndex: 1600 }}>
<DialogTitle>{constants.RENAME_FILE}</DialogTitle>
<DialogTitle>{t('RENAME_FILE')}</DialogTitle>
<DialogContent>
<SingleInputForm
initialValue={filename}
callback={onSubmit}
placeholder={constants.ENTER_FILE_NAME}
buttonText={constants.RENAME}
placeholder={t('ENTER_FILE_NAME')}
buttonText={t('RENAME')}
fieldType="text"
caption={extension}
secondaryButtonAction={closeEditMode}

View file

@ -9,9 +9,9 @@ import { MAX_CAPTION_SIZE } from 'constants/file';
import { Formik } from 'formik';
import { SmallLoadingSpinner } from '../styledComponents/SmallLoadingSpinner';
import * as Yup from 'yup';
import constants from 'utils/strings/constants';
import Close from '@mui/icons-material/Close';
import Done from '@mui/icons-material/Done';
import { useTranslation } from 'react-i18next';
interface formValues {
caption: string;
@ -27,6 +27,8 @@ export function RenderCaption({
scheduleUpdate: () => void;
refreshPhotoswipe: () => void;
}) {
const { t } = useTranslation();
const [caption, setCaption] = useState(
file?.pubMagicMetadata?.data.caption
);
@ -70,7 +72,7 @@ export function RenderCaption({
validationSchema={Yup.object().shape({
caption: Yup.string().max(
MAX_CAPTION_SIZE,
constants.CAPTION_CHARACTER_LIMIT
t('CAPTION_CHARACTER_LIMIT')
),
})}
validateOnBlur={false}
@ -90,7 +92,7 @@ export function RenderCaption({
name="caption"
type="text"
multiline
placeholder={constants.CAPTION_PLACEHOLDER}
placeholder={t('CAPTION_PLACEHOLDER')}
value={values.caption}
onChange={handleChange('caption')}
error={Boolean(errors.caption)}

View file

@ -1,5 +1,4 @@
import React, { useContext, useEffect, useState } from 'react';
import constants from 'utils/strings/constants';
import { RenderFileName } from './RenderFileName';
import { RenderCreationTime } from './RenderCreationTime';
import { Box, DialogProps, Link, Stack, styled } from '@mui/material';
@ -33,6 +32,7 @@ import { ObjectLabelList } from 'components/MachineLearning/ObjectList';
// import MLServiceFileInfoButton from 'components/MachineLearning/MLServiceFileInfoButton';
import { AppContext } from 'pages/_app';
import { useTranslation } from 'react-i18next';
export const FileInfoSidebar = styled((props: DialogProps) => (
<EnteDrawer {...props} anchor="right" />
@ -89,6 +89,8 @@ export function FileInfo({
collectionNameMap,
isTrashCollection,
}: Iprops) {
const { t } = useTranslation();
const appContext = useContext(AppContext);
const [location, setLocation] = useState<Location>(null);
const [parsedExifData, setParsedExifData] = useState<Record<string, any>>();
@ -165,11 +167,7 @@ export function FileInfo({
return (
<FileInfoSidebar open={showInfo} onClose={handleCloseInfo}>
<Titlebar
onClose={handleCloseInfo}
title={constants.INFO}
backIsClose
/>
<Titlebar onClose={handleCloseInfo} title={t('INFO')} backIsClose />
<Stack pt={1} pb={3} spacing={'20px'}>
<RenderCaption
shouldDisableEdits={shouldDisableEdits}
@ -206,7 +204,7 @@ export function FileInfo({
{location && (
<InfoItem
icon={<LocationOnOutlined />}
title={constants.LOCATION}
title={t('LOCATION')}
caption={
<Link
href={getOpenStreetMapLink({
@ -215,7 +213,7 @@ export function FileInfo({
})}
target="_blank"
sx={{ fontWeight: 'bold' }}>
{constants.SHOW_ON_MAP}
{t('SHOW_ON_MAP')}
</Link>
}
customEndButton={
@ -232,7 +230,7 @@ export function FileInfo({
)}
<InfoItem
icon={<TextSnippetOutlined />}
title={constants.DETAILS}
title={t('DETAILS')}
caption={
typeof exif === 'undefined' ? (
<EnteSpinner size={11.33} />
@ -244,10 +242,10 @@ export function FileInfo({
color: 'text.secondary',
fontWeight: 'bold',
}}>
{constants.VIEW_EXIF}
{t('VIEW_EXIF')}
</LinkButton>
) : (
constants.NO_EXIF
t('NO_EXIF')
)
}
hideEditOption

View file

@ -1,832 +0,0 @@
export {};
// import React, { useContext, useEffect, useRef, useState } from 'react';
// import Photoswipe from 'photoswipe';
// import PhotoswipeUIDefault from 'photoswipe/dist/photoswipe-ui-default';
// import classnames from 'classnames';
// import FavButton from 'components/FavButton';
// import {
// addToFavorites,
// removeFromFavorites,
// } from 'services/collectionService';
// import { updatePublicMagicMetadata } from 'services/fileService';
// import { EnteFile } from 'types/file';
// import constants from 'utils/strings/constants';
// import exifr from 'exifr';
// import Modal from 'react-bootstrap/Modal';
// import Button from 'react-bootstrap/Button';
// import styled from 'styled-components';
// import events from './events';
// import {
// changeFileCreationTime,
// changeFileName,
// downloadFile,
// formatDateTime,
// splitFilenameAndExtension,
// updateExistingFilePubMetadata,
// } from 'utils/file';
// import { Col, Form, FormCheck, FormControl } from 'react-bootstrap';
// import { prettyPrintExif } from 'utils/exif';
// import EditIcon from 'components/icons/EditIcon';
// import {
// FlexWrapper,
// FreeFlowText,
// IconButton,
// Label,
// Row,
// Value,
// } from 'components/Container';
// import { logError } from 'utils/sentry';
// import CloseIcon from 'components/icons/CloseIcon';
// import TickIcon from 'components/icons/TickIcon';
// import {
// PhotoPeopleList,
// UnidentifiedFaces,
// } from 'components/MachineLearning/PeopleList';
// import { Formik } from 'formik';
// import * as Yup from 'yup';
// import EnteSpinner from 'components/EnteSpinner';
// import EnteDateTimePicker from 'components/EnteDateTimePicker';
// // import { AppContext } from 'pages/_app';
// import { MAX_EDITED_FILE_NAME_LENGTH } from 'constants/file';
// import { sleep } from 'utils/common';
// import { PublicCollectionGalleryContext } from 'utils/publicCollectionGallery';
// import { GalleryContext } from 'pages/gallery';
// import { ObjectLabelList } from 'components/MachineLearning/ObjectList';
// import { WordList } from 'components/MachineLearning/WordList';
// import MLServiceFileInfoButton from 'components/MachineLearning/MLServiceFileInfoButton';
// const SmallLoadingSpinner = () => (
// <EnteSpinner
// style={{
// width: '20px',
// height: '20px',
// }}
// />
// );
// interface Iprops {
// isOpen: boolean;
// items: EnteFile[];
// currentIndex?: number;
// onClose?: (needUpdate: boolean) => void;
// gettingData: (instance: any, index: number, item: EnteFile) => void;
// id?: string;
// className?: string;
// favItemIds: Set<number>;
// isSharedCollection: boolean;
// isTrashCollection: boolean;
// }
// const LegendContainer = styled.div`
// display: flex;
// justify-content: space-between;
// `;
// const Legend = styled.span`
// font-size: 20px;
// color: #ddd;
// display: inline;
// `;
// const Pre = styled.pre`
// color: #aaa;
// padding: 7px 15px;
// `;
// const renderInfoItem = (label: string, value: string | JSX.Element) => (
// <Row>
// <Label width="30%">{label}</Label>
// <Value width="70%">{value}</Value>
// </Row>
// );
// function RenderCreationTime({
// shouldDisableEdits,
// file,
// scheduleUpdate,
// }: {
// shouldDisableEdits: boolean;
// file: EnteFile;
// scheduleUpdate: () => void;
// }) {
// const [loading, setLoading] = useState(false);
// const originalCreationTime = new Date(file?.metadata.creationTime / 1000);
// const [isInEditMode, setIsInEditMode] = useState(false);
// const [pickedTime, setPickedTime] = useState(originalCreationTime);
// const openEditMode = () => setIsInEditMode(true);
// const closeEditMode = () => setIsInEditMode(false);
// const saveEdits = async () => {
// try {
// setLoading(true);
// if (isInEditMode && file) {
// const unixTimeInMicroSec = pickedTime.getTime() * 1000;
// if (unixTimeInMicroSec === file?.metadata.creationTime) {
// closeEditMode();
// return;
// }
// let updatedFile = await changeFileCreationTime(
// file,
// unixTimeInMicroSec
// );
// updatedFile = (
// await updatePublicMagicMetadata([updatedFile])
// )[0];
// updateExistingFilePubMetadata(file, updatedFile);
// scheduleUpdate();
// }
// } catch (e) {
// logError(e, 'failed to update creationTime');
// } finally {
// closeEditMode();
// setLoading(false);
// }
// };
// const discardEdits = () => {
// setPickedTime(originalCreationTime);
// closeEditMode();
// };
// const handleChange = (newDate: Date) => {
// if (newDate instanceof Date) {
// setPickedTime(newDate);
// }
// };
// return (
// <>
// <Row>
// <Label width="30%">{constants.CREATION_TIME}</Label>
// <Value width={isInEditMode ? '50%' : '60%'}>
// {isInEditMode ? (
// <EnteDateTimePicker
// loading={loading}
// isInEditMode={isInEditMode}
// pickedTime={pickedTime}
// handleChange={handleChange}
// />
// ) : (
// formatDateTime(pickedTime)
// )}
// </Value>
// <Value
// width={isInEditMode ? '20%' : '10%'}
// style={{ cursor: 'pointer', marginLeft: '10px' }}>
// {!shouldDisableEdits &&
// (!isInEditMode ? (
// <IconButton onClick={openEditMode}>
// <EditIcon />
// </IconButton>
// ) : (
// <>
// <IconButton onClick={saveEdits}>
// {loading ? (
// <SmallLoadingSpinner />
// ) : (
// <TickIcon />
// )}
// </IconButton>
// <IconButton onClick={discardEdits}>
// <CloseIcon />
// </IconButton>
// </>
// ))}
// </Value>
// </Row>
// </>
// );
// }
// const getFileTitle = (filename, extension) => {
// if (extension) {
// return filename + '.' + extension;
// } else {
// return filename;
// }
// };
// interface formValues {
// filename: string;
// }
// const FileNameEditForm = ({ filename, saveEdits, discardEdits, extension }) => {
// const [loading, setLoading] = useState(false);
// const onSubmit = async (values: formValues) => {
// try {
// setLoading(true);
// await saveEdits(values.filename);
// } finally {
// setLoading(false);
// }
// };
// return (
// <Formik<formValues>
// initialValues={{ filename }}
// validationSchema={Yup.object().shape({
// filename: Yup.string()
// .required(constants.REQUIRED)
// .max(
// MAX_EDITED_FILE_NAME_LENGTH,
// constants.FILE_NAME_CHARACTER_LIMIT
// ),
// })}
// validateOnBlur={false}
// onSubmit={onSubmit}>
// {({ values, errors, handleChange, handleSubmit }) => (
// <Form noValidate onSubmit={handleSubmit}>
// <Form.Row>
// <Form.Group
// bsPrefix="ente-form-group"
// as={Col}
// xs={extension ? 7 : 8}>
// <Form.Control
// as="textarea"
// placeholder={constants.FILE_NAME}
// value={values.filename}
// onChange={handleChange('filename')}
// isInvalid={Boolean(errors.filename)}
// autoFocus
// disabled={loading}
// />
// <FormControl.Feedback
// type="invalid"
// style={{ textAlign: 'center' }}>
// {errors.filename}
// </FormControl.Feedback>
// </Form.Group>
// {extension && (
// <Form.Group
// bsPrefix="ente-form-group"
// as={Col}
// xs={1}
// controlId="formHorizontalFileName">
// <FlexWrapper style={{ padding: '5px' }}>
// {`.${extension}`}
// </FlexWrapper>
// </Form.Group>
// )}
// <Form.Group bsPrefix="ente-form-group" as={Col} xs={2}>
// <Value width={'16.67%'}>
// <IconButton type="submit" disabled={loading}>
// {loading ? (
// <SmallLoadingSpinner />
// ) : (
// <TickIcon />
// )}
// </IconButton>
// <IconButton
// onClick={discardEdits}
// disabled={loading}>
// <CloseIcon />
// </IconButton>
// </Value>
// </Form.Group>
// </Form.Row>
// </Form>
// )}
// </Formik>
// );
// };
// function RenderFileName({
// shouldDisableEdits,
// file,
// scheduleUpdate,
// }: {
// shouldDisableEdits: boolean;
// file: EnteFile;
// scheduleUpdate: () => void;
// }) {
// const originalTitle = file?.metadata.title;
// const [isInEditMode, setIsInEditMode] = useState(false);
// const [originalFileName, extension] =
// splitFilenameAndExtension(originalTitle);
// const [filename, setFilename] = useState(originalFileName);
// const openEditMode = () => setIsInEditMode(true);
// const closeEditMode = () => setIsInEditMode(false);
// const saveEdits = async (newFilename: string) => {
// try {
// if (file) {
// if (filename === newFilename) {
// closeEditMode();
// return;
// }
// setFilename(newFilename);
// const newTitle = getFileTitle(newFilename, extension);
// let updatedFile = await changeFileName(file, newTitle);
// updatedFile = (
// await updatePublicMagicMetadata([updatedFile])
// )[0];
// updateExistingFilePubMetadata(file, updatedFile);
// scheduleUpdate();
// }
// } catch (e) {
// logError(e, 'failed to update file name');
// } finally {
// closeEditMode();
// }
// };
// return (
// <>
// <Row>
// <Label width="30%">{constants.FILE_NAME}</Label>
// {!isInEditMode ? (
// <>
// <Value width="60%">
// <FreeFlowText>
// {getFileTitle(filename, extension)}
// </FreeFlowText>
// </Value>
// {!shouldDisableEdits && (
// <Value
// width="10%"
// style={{
// cursor: 'pointer',
// marginLeft: '10px',
// }}>
// <IconButton onClick={openEditMode}>
// <EditIcon />
// </IconButton>
// </Value>
// )}
// </>
// ) : (
// <FileNameEditForm
// extension={extension}
// filename={filename}
// saveEdits={saveEdits}
// discardEdits={closeEditMode}
// />
// )}
// </Row>
// </>
// );
// }
// function ExifData(props: { exif: any }) {
// const { exif } = props;
// const [showAll, setShowAll] = useState(false);
// const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
// setShowAll(e.target.checked);
// };
// const renderAllValues = () => <Pre>{exif.raw}</Pre>;
// const renderSelectedValues = () => (
// <>
// {exif?.Make &&
// exif?.Model &&
// renderInfoItem(constants.DEVICE, `${exif.Make} ${exif.Model}`)}
// {exif?.ImageWidth &&
// exif?.ImageHeight &&
// renderInfoItem(
// constants.IMAGE_SIZE,
// `${exif.ImageWidth} x ${exif.ImageHeight}`
// )}
// {exif?.Flash && renderInfoItem(constants.FLASH, exif.Flash)}
// {exif?.FocalLength &&
// renderInfoItem(
// constants.FOCAL_LENGTH,
// exif.FocalLength.toString()
// )}
// {exif?.ApertureValue &&
// renderInfoItem(
// constants.APERTURE,
// exif.ApertureValue.toString()
// )}
// {exif?.ISOSpeedRatings &&
// renderInfoItem(constants.ISO, exif.ISOSpeedRatings.toString())}
// </>
// );
// return (
// <>
// <LegendContainer>
// <Legend>{constants.EXIF}</Legend>
// <FormCheck>
// <FormCheck.Label>
// <FormCheck.Input onChange={changeHandler} />
// {constants.SHOW_ALL}
// </FormCheck.Label>
// </FormCheck>
// </LegendContainer>
// {showAll ? renderAllValues() : renderSelectedValues()}
// </>
// );
// }
// function InfoModal({
// shouldDisableEdits,
// showInfo,
// handleCloseInfo,
// items,
// photoSwipe,
// metadata,
// exif,
// scheduleUpdate,
// }) {
// // const appContext = useContext(AppContext);
// const [updateMLDataIndex, setUpdateMLDataIndex] = useState(0);
// return (
// <Modal show={showInfo} onHide={handleCloseInfo}>
// <Modal.Header closeButton>
// <Modal.Title>{constants.INFO}</Modal.Title>
// </Modal.Header>
// <Modal.Body>
// <div>
// <Legend>{constants.METADATA}</Legend>
// </div>
// {renderInfoItem(
// constants.FILE_ID,
// items[photoSwipe?.getCurrentIndex()]?.id
// )}
// {metadata?.title && (
// <RenderFileName
// shouldDisableEdits={shouldDisableEdits}
// file={items[photoSwipe?.getCurrentIndex()]}
// scheduleUpdate={scheduleUpdate}
// />
// )}
// {metadata?.creationTime && (
// <RenderCreationTime
// shouldDisableEdits={shouldDisableEdits}
// file={items[photoSwipe?.getCurrentIndex()]}
// scheduleUpdate={scheduleUpdate}
// />
// )}
// {metadata?.modificationTime &&
// renderInfoItem(
// constants.UPDATED_ON,
// formatDateTime(metadata.modificationTime / 1000)
// )}
// {metadata?.longitude > 0 &&
// metadata?.longitude > 0 &&
// renderInfoItem(
// constants.LOCATION,
// <a
// href={`https://www.openstreetmap.org/?mlat=${metadata.latitude}&mlon=${metadata.longitude}#map=15/${metadata.latitude}/${metadata.longitude}`}
// target="_blank"
// rel="noopener noreferrer">
// {constants.SHOW_MAP}
// </a>
// )}
// {/* {appContext.mlSearchEnabled && ( */}
// <>
// <div>
// <Legend>{constants.PEOPLE}</Legend>
// </div>
// <PhotoPeopleList
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// />
// <div>
// <Legend>{constants.UNIDENTIFIED_FACES}</Legend>
// </div>
// <UnidentifiedFaces
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// />
// <div>
// <Legend>{constants.OBJECTS}</Legend>
// <ObjectLabelList
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// />
// </div>
// <div>
// <Legend>{constants.TEXT}</Legend>
// <WordList
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// />
// </div>
// <MLServiceFileInfoButton
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// setUpdateMLDataIndex={setUpdateMLDataIndex}
// />
// </>
// {/* )} */}
// {exif && (
// <>
// <ExifData exif={exif} />
// </>
// )}
// </Modal.Body>
// <Modal.Footer>
// <Button variant="outline-secondary" onClick={handleCloseInfo}>
// {constants.CLOSE}
// </Button>
// </Modal.Footer>
// </Modal>
// );
// }
// function PhotoSwipe(props: Iprops) {
// const pswpElement = useRef<HTMLDivElement>();
// const [photoSwipe, setPhotoSwipe] = useState<Photoswipe<any>>();
// const { isOpen, items } = props;
// const [isFav, setIsFav] = useState(false);
// const [showInfo, setShowInfo] = useState(false);
// const [metadata, setMetaData] = useState<EnteFile['metadata']>(null);
// const [exif, setExif] = useState<any>(null);
// const needUpdate = useRef(false);
// const publicCollectionGalleryContext = useContext(
// PublicCollectionGalleryContext
// );
// const galleryContext = useContext(GalleryContext);
// useEffect(() => {
// if (!pswpElement) return;
// if (isOpen) {
// openPhotoSwipe();
// }
// if (!isOpen) {
// closePhotoSwipe();
// }
// return () => {
// closePhotoSwipe();
// };
// }, [isOpen]);
// useEffect(() => {
// updateItems(items);
// }, [items]);
// // useEffect(() => {
// // if (photoSwipe) {
// // photoSwipe.options.arrowKeys = !showInfo;
// // photoSwipe.options.escKey = !showInfo;
// // }
// // }, [showInfo]);
// function updateFavButton() {
// setIsFav(isInFav(this?.currItem));
// }
// const openPhotoSwipe = () => {
// const { items, currentIndex } = props;
// const options = {
// history: false,
// maxSpreadZoom: 5,
// index: currentIndex,
// showHideOpacity: true,
// getDoubleTapZoom(isMouseClick, item) {
// if (isMouseClick) {
// return 2.5;
// }
// // zoom to original if initial zoom is less than 0.7x,
// // otherwise to 1.5x, to make sure that double-tap gesture always zooms image
// return item.initialZoomLevel < 0.7 ? 1 : 1.5;
// },
// getThumbBoundsFn: (index) => {
// try {
// const file = items[index];
// const ele = document.getElementById(`thumb-${file.id}`);
// if (ele) {
// const rect = ele.getBoundingClientRect();
// const pageYScroll =
// window.pageYOffset ||
// document.documentElement.scrollTop;
// return {
// x: rect.left,
// y: rect.top + pageYScroll,
// w: rect.width,
// };
// }
// return null;
// } catch (e) {
// return null;
// }
// },
// };
// const photoSwipe = new Photoswipe(
// pswpElement.current,
// PhotoswipeUIDefault,
// items,
// options
// );
// events.forEach((event) => {
// const callback = props[event];
// if (callback || event === 'destroy') {
// photoSwipe.listen(event, function (...args) {
// if (callback) {
// args.unshift(this);
// callback(...args);
// }
// if (event === 'destroy') {
// handleClose();
// }
// if (event === 'close') {
// handleClose();
// }
// });
// }
// });
// photoSwipe.listen('beforeChange', function () {
// updateInfo.call(this);
// updateFavButton.call(this);
// });
// photoSwipe.listen('resize', checkExifAvailable);
// photoSwipe.init();
// needUpdate.current = false;
// setPhotoSwipe(photoSwipe);
// };
// const closePhotoSwipe = () => {
// if (photoSwipe) photoSwipe.close();
// };
// const handleClose = () => {
// const { onClose } = props;
// if (typeof onClose === 'function') {
// onClose(needUpdate.current);
// }
// const videoTags = document.getElementsByTagName('video');
// for (const videoTag of videoTags) {
// videoTag.pause();
// }
// handleCloseInfo();
// };
// const isInFav = (file) => {
// const { favItemIds } = props;
// if (favItemIds && file) {
// return favItemIds.has(file.id);
// }
// return false;
// };
// const onFavClick = async (file) => {
// const { favItemIds } = props;
// if (!isInFav(file)) {
// favItemIds.add(file.id);
// addToFavorites(file);
// setIsFav(true);
// } else {
// favItemIds.delete(file.id);
// removeFromFavorites(file);
// setIsFav(false);
// }
// needUpdate.current = true;
// };
// const updateItems = (items = []) => {
// if (photoSwipe) {
// photoSwipe.items.length = 0;
// items.forEach((item) => {
// photoSwipe.items.push(item);
// });
// photoSwipe.invalidateCurrItems();
// // photoSwipe.updateSize(true);
// }
// };
// const checkExifAvailable = async () => {
// setExif(null);
// await sleep(100);
// try {
// const img: HTMLImageElement = document.querySelector(
// '.pswp__img:not(.pswp__img--placeholder)'
// );
// if (img) {
// const exifData = await exifr.parse(img);
// if (!exifData) {
// return;
// }
// exifData.raw = prettyPrintExif(exifData);
// setExif(exifData);
// }
// } catch (e) {
// logError(e, 'exifr parsing failed');
// }
// };
// function updateInfo() {
// const file: EnteFile = this?.currItem;
// if (file?.metadata) {
// setMetaData(file.metadata);
// setExif(null);
// checkExifAvailable();
// }
// }
// const handleCloseInfo = () => {
// setShowInfo(false);
// };
// const handleOpenInfo = () => {
// setShowInfo(true);
// };
// const downloadFileHelper = async (file) => {
// galleryContext.startLoading();
// await downloadFile(
// file,
// publicCollectionGalleryContext.accessedThroughSharedURL,
// publicCollectionGalleryContext.token
// );
// galleryContext.finishLoading();
// };
// const scheduleUpdate = () => (needUpdate.current = true);
// const { id } = props;
// let { className } = props;
// className = classnames(['pswp', className]).trim();
// return (
// <>
// <div
// id={id}
// className={className}
// tabIndex={Number('-1')}
// role="dialog"
// aria-hidden="true"
// ref={pswpElement}>
// <div className="pswp__bg" />
// <div className="pswp__scroll-wrap">
// <div className="pswp__container">
// <div className="pswp__item" />
// <div className="pswp__item" />
// <div className="pswp__item" />
// </div>
// <div className="pswp__ui pswp__ui--hidden">
// <div className="pswp__top-bar">
// <div className="pswp__counter" />
// <button
// className="pswp__button pswp__button--close"
// title={constants.CLOSE}
// />
// <button
// className="pswp-custom download-btn"
// title={constants.DOWNLOAD}
// onClick={() =>
// downloadFileHelper(photoSwipe.currItem)
// }
// />
// <button
// className="pswp__button pswp__button--fs"
// title={constants.TOGGLE_FULLSCREEN}
// />
// <button
// className="pswp__button pswp__button--zoom"
// title={constants.ZOOM_IN_OUT}
// />
// {!props.isSharedCollection &&
// !props.isTrashCollection && (
// <FavButton
// size={44}
// isClick={isFav}
// onClick={() => {
// onFavClick(photoSwipe?.currItem);
// }}
// />
// )}
// <button
// className="pswp-custom info-btn"
// title={constants.INFO}
// onClick={handleOpenInfo}
// />
// <div className="pswp__preloader">
// <div className="pswp__preloader__icn">
// <div className="pswp__preloader__cut">
// <div className="pswp__preloader__donut" />
// </div>
// </div>
// </div>
// </div>
// <div className="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
// <div className="pswp__share-tooltip" />
// </div>
// <button
// className="pswp__button pswp__button--arrow--left"
// title={constants.PREVIOUS}
// />
// <button
// className="pswp__button pswp__button--arrow--right"
// title={constants.NEXT}
// />
// <div className="pswp__caption">
// <div />
// </div>
// </div>
// </div>
// </div>
// <InfoModal
// shouldDisableEdits={props.isSharedCollection}
// showInfo={showInfo}
// handleCloseInfo={handleCloseInfo}
// items={items}
// photoSwipe={photoSwipe}
// metadata={metadata}
// exif={exif}
// scheduleUpdate={scheduleUpdate}
// />
// </>
// );
// }
// export default PhotoSwipe;

View file

@ -7,7 +7,6 @@ import {
removeFromFavorites,
} from 'services/collectionService';
import { EnteFile } from 'types/file';
import constants from 'utils/strings/constants';
import exifr from 'exifr';
import {
downloadFile,
@ -40,6 +39,7 @@ import { styled } from '@mui/material';
import { addLocalLog } from 'utils/logging';
import ContentCopy from '@mui/icons-material/ContentCopy';
import ChevronLeft from '@mui/icons-material/ChevronLeft';
import { useTranslation } from 'react-i18next';
interface PhotoswipeFullscreenAPI {
enter: () => void;
@ -77,6 +77,8 @@ interface Iprops {
}
function PhotoViewer(props: Iprops) {
const { t } = useTranslation();
const pswpElement = useRef<HTMLDivElement>();
const [photoSwipe, setPhotoSwipe] =
useState<Photoswipe<Photoswipe.Options>>();
@ -539,7 +541,7 @@ function PhotoViewer(props: Iprops) {
onMouseEnter={livePhotoBtnOptions.show}
onMouseLeave={livePhotoBtnOptions.hide}
disabled={livePhotoBtnOptions.loading}>
{livePhotoBtnHTML} {constants.LIVE}
{livePhotoBtnHTML} {t('LIVE')}
</LivePhotoBtn>
)}
<div className="pswp__container">
@ -553,13 +555,13 @@ function PhotoViewer(props: Iprops) {
<button
className="pswp__button pswp__button--close"
title={constants.CLOSE_OPTION}
title={t('CLOSE_OPTION')}
/>
{props.enableDownload && (
<button
className="pswp__button pswp__button--custom"
title={constants.DOWNLOAD_OPTION}
title={t('DOWNLOAD_OPTION')}
onClick={() =>
downloadFileHelper(photoSwipe.currItem)
}>
@ -569,7 +571,7 @@ function PhotoViewer(props: Iprops) {
{props.enableDownload && shouldShowCopyOption && (
<button
className="pswp__button pswp__button--custom"
title={constants.COPY_OPTION}
title={t('COPY_OPTION')}
onClick={() =>
copyToClipboardHelper(
photoSwipe.currItem as EnteFile
@ -582,7 +584,7 @@ function PhotoViewer(props: Iprops) {
!props.isTrashCollection && (
<button
className="pswp__button pswp__button--custom"
title={constants.DELETE_OPTION}
title={t('DELETE_OPTION')}
onClick={() => {
confirmTrashFile(
photoSwipe?.currItem as EnteFile
@ -593,17 +595,17 @@ function PhotoViewer(props: Iprops) {
)}
<button
className="pswp__button pswp__button--zoom"
title={constants.ZOOM_IN_OUT}
title={t('ZOOM_IN_OUT')}
/>
<button
className="pswp__button pswp__button--fs"
title={constants.TOGGLE_FULLSCREEN}
title={t('TOGGLE_FULLSCREEN')}
/>
{!props.isIncomingSharedCollection && (
<button
className="pswp__button pswp__button--custom"
title={constants.INFO_OPTION}
title={t('INFO_OPTION')}
onClick={handleOpenInfo}>
<InfoIcon fontSize="small" />
</button>
@ -613,8 +615,8 @@ function PhotoViewer(props: Iprops) {
<button
title={
isFav
? constants.UNFAVORITE_OPTION
: constants.FAVORITE_OPTION
? t('UNFAVORITE_OPTION')
: t('FAVORITE_OPTION')
}
className="pswp__button pswp__button--custom"
onClick={() => {
@ -643,12 +645,12 @@ function PhotoViewer(props: Iprops) {
</div>
<button
className="pswp__button pswp__button--arrow--left"
title={constants.PREVIOUS}>
title={t('PREVIOUS')}>
<ChevronLeft sx={{ pointerEvents: 'none' }} />
</button>
<button
className="pswp__button pswp__button--arrow--right"
title={constants.NEXT}>
title={t('NEXT')}>
<ChevronRight sx={{ pointerEvents: 'none' }} />
</button>
<div className="pswp__caption pswp-custom-caption-container">

View file

@ -1,175 +0,0 @@
export {}; // import React, { useContext, useEffect, useState } from 'react';
// import constants from 'utils/strings/constants';
// import { formatDateTime } from 'utils/time';
// import { RenderFileName } from './RenderFileName';
// import { ExifData } from './ExifData';
// import { RenderCreationTime } from './RenderCreationTime';
// import { RenderInfoItem } from './RenderInfoItem';
// import DialogTitleWithCloseButton from 'components/DialogBox/TitleWithCloseButton';
// import { Dialog, DialogContent, Link, styled, Typography } from '@mui/material';
// import { AppContext } from 'pages/_app';
// import { Location, Metadata } from 'types/upload';
// import Photoswipe from 'photoswipe';
// import { getEXIFLocation } from 'services/upload/exifService';
// import {
// PhotoPeopleList,
// UnidentifiedFaces,
// } from 'components/MachineLearning/PeopleList';
// import { ObjectLabelList } from 'components/MachineLearning/ObjectList';
// import { WordList } from 'components/MachineLearning/WordList';
// import MLServiceFileInfoButton from 'components/MachineLearning/MLServiceFileInfoButton';
// const FileInfoDialog = styled(Dialog)(({ theme }) => ({
// zIndex: 1501,
// '& .MuiDialog-container': {
// alignItems: 'flex-start',
// },
// '& .MuiDialog-paper': {
// padding: theme.spacing(2),
// },
// }));
// const Legend = styled('span')`
// font-size: 20px;
// color: #ddd;
// display: inline;
// `;
// interface Iprops {
// shouldDisableEdits: boolean;
// showInfo: boolean;
// handleCloseInfo: () => void;
// items: any[];
// photoSwipe: Photoswipe<Photoswipe.Options>;
// metadata: Metadata;
// exif: any;
// scheduleUpdate: () => void;
// }
// export function FileInfo({
// shouldDisableEdits,
// showInfo,
// handleCloseInfo,
// items,
// photoSwipe,
// metadata,
// exif,
// scheduleUpdate,
// }: Iprops) {
// const appContext = useContext(AppContext);
// const [location, setLocation] = useState<Location>(null);
// const [updateMLDataIndex, setUpdateMLDataIndex] = useState(0);
// useEffect(() => {
// if (!location && metadata) {
// if (metadata.longitude || metadata.longitude === 0) {
// setLocation({
// latitude: metadata.latitude,
// longitude: metadata.longitude,
// });
// }
// }
// }, [metadata]);
// useEffect(() => {
// if (!location && exif) {
// const exifLocation = getEXIFLocation(exif);
// if (exifLocation.latitude || exifLocation.latitude === 0) {
// setLocation(exifLocation);
// }
// }
// }, [exif]);
// return (
// <FileInfoDialog
// open={showInfo}
// onClose={handleCloseInfo}
// fullScreen={appContext.isMobile}>
// <DialogTitleWithCloseButton onClose={handleCloseInfo}>
// {constants.INFO}
// </DialogTitleWithCloseButton>
// <DialogContent>
// <Typography variant="subtitle" mb={1}>
// {constants.METADATA}
// </Typography>
// {RenderInfoItem(
// constants.FILE_ID,
// items[photoSwipe?.getCurrentIndex()]?.id
// )}
// {metadata?.title && (
// <RenderFileName
// shouldDisableEdits={shouldDisableEdits}
// file={items[photoSwipe?.getCurrentIndex()]}
// scheduleUpdate={scheduleUpdate}
// />
// )}
// {metadata?.creationTime && (
// <RenderCreationTime
// shouldDisableEdits={shouldDisableEdits}
// file={items[photoSwipe?.getCurrentIndex()]}
// scheduleUpdate={scheduleUpdate}
// />
// )}
// {metadata?.modificationTime &&
// RenderInfoItem(
// constants.UPDATED_ON,
// formatDateTime(metadata.modificationTime / 1000)
// )}
// {location &&
// RenderInfoItem(
// constants.LOCATION,
// <Link
// href={`https://www.openstreetmap.org/?mlat=${metadata.latitude}&mlon=${metadata.longitude}#map=15/${metadata.latitude}/${metadata.longitude}`}
// target="_blank"
// rel="noopener noreferrer">
// {constants.SHOW_MAP}
// </Link>
// )}
// {appContext.mlSearchEnabled && (
// <>
// <div>
// <Legend>{constants.PEOPLE}</Legend>
// </div>
// <PhotoPeopleList
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// />
// <div>
// <Legend>{constants.UNIDENTIFIED_FACES}</Legend>
// </div>
// <UnidentifiedFaces
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// />
// <div>
// <Legend>{constants.OBJECTS}</Legend>
// <ObjectLabelList
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// />
// </div>
// <div>
// <Legend>{constants.TEXT}</Legend>
// <WordList
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// />
// </div>
// <MLServiceFileInfoButton
// file={items[photoSwipe?.getCurrentIndex()]}
// updateMLDataIndex={updateMLDataIndex}
// setUpdateMLDataIndex={setUpdateMLDataIndex}
// />
// </>
// )}
// {exif && (
// <>
// <ExifData exif={exif} />
// </>
// )}
// </DialogContent>
// </FileInfoDialog>
// );
// }

View file

@ -1,7 +1,6 @@
import React, { useContext, useEffect, useState } from 'react';
import { downloadAsFile } from 'utils/file';
import { getRecoveryKey } from 'utils/crypto';
import constants from 'utils/strings/constants';
import CodeBlock from '../CodeBlock';
import {
Button,
@ -14,6 +13,7 @@ import * as bip39 from 'bip39';
import { DashedBorderWrapper } from './styledComponents';
import { AppContext } from 'pages/_app';
import DialogTitleWithCloseButton from 'components/DialogBox/TitleWithCloseButton';
import { useTranslation } from 'react-i18next';
// mobile client library only supports english.
bip39.setDefaultWordlist('english');
@ -27,6 +27,8 @@ interface Props {
}
function RecoveryKey({ somethingWentWrong, ...props }: Props) {
const { t } = useTranslation();
const appContext = useContext(AppContext);
const [recoveryKey, setRecoveryKey] = useState(null);
@ -58,25 +60,23 @@ function RecoveryKey({ somethingWentWrong, ...props }: Props) {
onClose={props.onHide}
maxWidth="xs">
<DialogTitleWithCloseButton onClose={props.onHide}>
{constants.RECOVERY_KEY}
{t('RECOVERY_KEY')}
</DialogTitleWithCloseButton>
<DialogContent>
<Typography mb={3}>
{constants.RECOVERY_KEY_DESCRIPTION}
</Typography>
<Typography mb={3}>{t('RECOVERY_KEY_DESCRIPTION')}</Typography>
<DashedBorderWrapper>
<CodeBlock code={recoveryKey} />
<Typography m={2}>
{constants.KEY_NOT_STORED_DISCLAIMER}
{t('KEY_NOT_STORED_DISCLAIMER')}
</Typography>
</DashedBorderWrapper>
</DialogContent>
<DialogActions>
<Button color="secondary" size="large" onClick={props.onHide}>
{constants.SAVE_LATER}
{t('SAVE_LATER')}
</Button>
<Button color="accent" size="large" onClick={onSaveClick}>
{constants.SAVE}
{t('SAVE')}
</Button>
</DialogActions>
</Dialog>

View file

@ -7,7 +7,7 @@ import { Row } from 'components/Container';
import { Col } from 'react-bootstrap';
import { AppContext } from 'pages/_app';
import styled from '@mui/styled-engine';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
const { Menu } = components;
@ -30,6 +30,7 @@ const Caption = styled('span')`
`;
const MenuWithPeople = (props) => {
const { t } = useTranslation();
const appContext = useContext(AppContext);
// addLogLine("props.selectProps.options: ", selectRef);
const peopleSuggestions = props.selectProps.options.filter(
@ -48,7 +49,7 @@ const MenuWithPeople = (props) => {
{((appContext.mlSearchEnabled && indexStatus) ||
(people && people.length > 0)) && (
<LegendRow>
<Legend>{constants.PEOPLE}</Legend>
<Legend>{t('PEOPLE')}</Legend>
</LegendRow>
)}
{appContext.mlSearchEnabled && indexStatus && (

View file

@ -13,7 +13,6 @@ import {
SearchOption,
SuggestionType,
} from 'types/search';
import constants from 'utils/strings/constants';
import { ValueContainerWithIcon } from './valueContainerWithIcon';
import { SelectStyles } from '../../../../styles/search';
import AsyncSelect from 'react-select/async';
@ -25,6 +24,7 @@ import { OptionWithInfo } from './optionWithInfo';
import { SearchInputWrapper } from '../styledComponents';
import MenuWithPeople from './MenuWithPeople';
import { Person, Thing, WordGroup } from 'types/machineLearning';
import { useTranslation } from 'react-i18next';
interface Iprops {
isOpen: boolean;
@ -36,6 +36,8 @@ interface Iprops {
}
export default function SearchInput(props: Iprops) {
const { t } = useTranslation();
const selectRef = useRef(null);
const [value, setValue] = useState<SearchOption>(null);
const appContext = useContext(AppContext);
@ -140,7 +142,7 @@ export default function SearchInput(props: Iprops) {
/>
),
}}
placeholder={constants.SEARCH_HINT()}
placeholder={<span>{t('SEARCH_HINT')}</span>}
loadOptions={getOptions}
onChange={handleChange}
onFocus={handleOnFocus}

View file

@ -4,7 +4,8 @@ import { Box, Divider, Stack, Typography } from '@mui/material';
import { FreeFlowText, SpaceBetweenFlex } from 'components/Container';
import CollectionCard from 'components/Collections/CollectionCard';
import { ResultPreviewTile } from 'components/Collections/styledComponents';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import { components } from 'react-select';
const { Option } = components;
@ -15,37 +16,41 @@ export const OptionWithInfo = (props) => (
</Option>
);
const LabelWithInfo = ({ data }: { data: SearchOption }) =>
!data.hide && (
<>
<Box className="main" px={2} py={1}>
<Typography variant="caption" mb={1}>
{constants.SEARCH_TYPE(data.type)}
</Typography>
<SpaceBetweenFlex>
<Box mr={1}>
<FreeFlowText>
<Typography fontWeight={'bold'}>
{data.label}
const LabelWithInfo = ({ data }: { data: SearchOption }) => {
const { t } = useTranslation();
return (
!data.hide && (
<>
<Box className="main" px={2} py={1}>
<Typography variant="caption" mb={1}>
{t('SEARCH_TYPE', { type: data.type })}
</Typography>
<SpaceBetweenFlex>
<Box mr={1}>
<FreeFlowText>
<Typography fontWeight={'bold'}>
{data.label}
</Typography>
</FreeFlowText>
<Typography color="text.secondary">
{t('PHOTO_COUNT', { count: data.fileCount })}
</Typography>
</FreeFlowText>
<Typography color="text.secondary">
{constants.PHOTO_COUNT(data.fileCount)}
</Typography>
</Box>
</Box>
<Stack direction={'row'} spacing={1}>
{data.previewFiles.map((file) => (
<CollectionCard
key={file.id}
latestFile={file}
onClick={() => null}
collectionTile={ResultPreviewTile}
/>
))}
</Stack>
</SpaceBetweenFlex>
</Box>
<Divider sx={{ mx: 2, my: 1 }} />
</>
<Stack direction={'row'} spacing={1}>
{data.previewFiles.map((file) => (
<CollectionCard
key={file.id}
latestFile={file}
onClick={() => null}
collectionTile={ResultPreviewTile}
/>
))}
</Stack>
</SpaceBetweenFlex>
</Box>
<Divider sx={{ mx: 2, my: 1 }} />
</>
)
);
};

View file

@ -1,14 +1,16 @@
import React from 'react';
import { CollectionInfo } from 'components/Collections/CollectionInfo';
import constants from 'utils/strings/constants';
import { Typography } from '@mui/material';
import { SearchResultSummary } from 'types/search';
import { CollectionInfoBarWrapper } from 'components/Collections/styledComponents';
import { useTranslation } from 'react-i18next';
interface Iprops {
searchResultSummary: SearchResultSummary;
}
export default function SearchResultInfo({ searchResultSummary }: Iprops) {
const { t } = useTranslation();
if (!searchResultSummary) {
return <></>;
}
@ -18,7 +20,7 @@ export default function SearchResultInfo({ searchResultSummary }: Iprops) {
return (
<CollectionInfoBarWrapper>
<Typography variant="subtitle" color="text.secondary">
{constants.SEARCH_RESULTS}
{t('SEARCH_RESULTS')}
</Typography>
<CollectionInfo name={optionName} fileCount={fileCount} />
</CollectionInfoBarWrapper>

View file

@ -1,496 +0,0 @@
export {};
// import React, { useContext, useEffect, useRef, useState } from 'react';
// import styled from 'styled-components';
// import AsyncSelect from 'react-select/async';
// import { components } from 'react-select';
// import debounce from 'debounce-promise';
// import {
// getAllPeopleSuggestion,
// getHolidaySuggestion,
// getIndexStatusSuggestion,
// getYearSuggestion,
// parseHumanDate,
// searchCollection,
// searchFiles,
// searchLocation,
// searchText,
// searchThing,
// } from 'services/searchService';
// import { getFormattedDate, isInsideBox } from 'utils/search';
// import constants from 'utils/strings/constants';
// import LocationIcon from './icons/LocationIcon';
// import DateIcon from './icons/DateIcon';
// import SearchIcon from './icons/SearchIcon';
// import CloseIcon from './icons/CloseIcon';
// import { Collection } from 'types/collection';
// import CollectionIcon from './icons/CollectionIcon';
// import ImageIcon from './icons/ImageIcon';
// import VideoIcon from './icons/VideoIcon';
// import { IconButton, Row } from './Container';
// import { EnteFile } from 'types/file';
// import { Suggestion, SuggestionType, DateValue, Bbox } from 'types/search';
// import { Search, SearchStats } from 'types/gallery';
// import { FILE_TYPE } from 'constants/file';
// import { GalleryContext } from 'pages/gallery';
// import { AppContext } from 'pages/_app';
// import { Col } from 'react-bootstrap';
// import { Person, Thing, WordGroup } from 'types/machineLearning';
// import { IndexStatus } from 'types/machineLearning/ui';
// import { PeopleList } from './MachineLearning/PeopleList';
// import ObjectIcon from './icons/ObjectIcon';
// import TextIcon from './icons/TextIcon';
// const Wrapper = styled.div<{ isDisabled: boolean; isOpen: boolean }>`
// position: fixed;
// top: 0;
// z-index: 1000;
// display: ${({ isOpen }) => (isOpen ? 'flex' : 'none')};
// width: 100%;
// background: #111;
// @media (min-width: 625px) {
// display: flex;
// width: calc(100vw - 140px);
// margin: 0 70px;
// }
// align-items: center;
// min-height: 64px;
// transition: opacity 1s ease;
// opacity: ${(props) => (props.isDisabled ? 0 : 1)};
// margin-bottom: 10px;
// `;
// const SearchButton = styled.div<{ isOpen: boolean }>`
// display: none;
// @media (max-width: 624px) {
// display: ${({ isOpen }) => (!isOpen ? 'flex' : 'none')};
// right: 80px;
// cursor: pointer;
// position: fixed;
// top: 0;
// z-index: 1000;
// align-items: center;
// min-height: 64px;
// }
// `;
// const SearchStatsContainer = styled.div`
// display: flex;
// justify-content: center;
// align-items: center;
// color: #979797;
// margin-bottom: 8px;
// `;
// const SearchInput = styled.div`
// width: 100%;
// display: flex;
// align-items: center;
// max-width: 484px;
// margin: auto;
// `;
// const Legend = styled.span`
// font-size: 20px;
// color: #ddd;
// display: inline;
// padding: 8px 12px;
// `;
// const Caption = styled.span`
// font-size: 12px;
// display: inline;
// padding: 8px 12px;
// `;
// const LegendRow = styled(Row)`
// align-items: center;
// justify-content: space-between;
// margin-bottom: 0px;
// `;
// interface Props {
// isOpen: boolean;
// isFirstFetch: boolean;
// setOpen: (value: boolean) => void;
// setSearch: (search: Search) => void;
// searchStats: SearchStats;
// collections: Collection[];
// setActiveCollection: (id: number) => void;
// files: EnteFile[];
// }
// export default function SearchBar(props: Props) {
// const selectRef = useRef(null);
// const [value, setValue] = useState<Suggestion>(null);
// const appContext = useContext(AppContext);
// const galleryContext = useContext(GalleryContext);
// const handleChange = (value) => {
// setValue(value);
// };
// // TODO: HACK as AsyncSelect does not support default options reloading on focus/click
// // unwanted side effect: placeholder is not shown on focus/click
// // https://github.com/JedWatson/react-select/issues/1879
// // for correct fix AsyncSelect can be extended to support default options reloading on focus/click
// const handleOnFocus = () => {
// if (appContext.mlSearchEnabled) {
// const emptySearch = ' ';
// selectRef.current.state.inputValue = emptySearch;
// selectRef.current.select.state.inputValue = emptySearch;
// selectRef.current.handleInputChange(emptySearch);
// }
// };
// useEffect(() => search(value), [value]);
// // = =========================
// // Functionality
// // = =========================
// const getAutoCompleteSuggestions = async (searchPhrase: string) => {
// const options: Array<Suggestion> = [];
// searchPhrase = searchPhrase.trim().toLowerCase();
// if (appContext.mlSearchEnabled) {
// options.push(await getIndexStatusSuggestion());
// options.push(...(await getAllPeopleSuggestion()));
// }
// if (!searchPhrase?.length) {
// return options;
// }
// options.push(...getHolidaySuggestion(searchPhrase));
// options.push(...getYearSuggestion(searchPhrase));
// const searchedDates = parseHumanDate(searchPhrase);
// options.push(
// ...searchedDates.map((searchedDate) => ({
// type: SuggestionType.DATE,
// value: searchedDate,
// label: getFormattedDate(searchedDate),
// }))
// );
// const collectionResults = searchCollection(
// searchPhrase,
// props.collections
// );
// options.push(
// ...collectionResults.map(
// (searchResult) =>
// ({
// type: SuggestionType.COLLECTION,
// value: searchResult.id,
// label: searchResult.name,
// } as Suggestion)
// )
// );
// const fileResults = searchFiles(searchPhrase, props.files);
// options.push(
// ...fileResults.map((file) => ({
// type:
// file.type === FILE_TYPE.IMAGE
// ? SuggestionType.IMAGE
// : SuggestionType.VIDEO,
// value: file.index,
// label: file.title,
// }))
// );
// const locationResults = await searchLocation(searchPhrase);
// const filteredLocationWithFiles = locationResults.filter(
// (locationResult) =>
// props.files.find((file) =>
// isInsideBox(file.metadata, locationResult.bbox)
// )
// );
// options.push(
// ...filteredLocationWithFiles.map(
// (searchResult) =>
// ({
// type: SuggestionType.LOCATION,
// value: searchResult.bbox,
// label: searchResult.place,
// } as Suggestion)
// )
// );
// const thingResults = await searchThing(searchPhrase);
// options.push(
// ...thingResults.map(
// (searchResult) =>
// ({
// type: SuggestionType.THING,
// value: searchResult,
// label: searchResult.className,
// } as Suggestion)
// )
// );
// const textResults = await searchText(searchPhrase);
// options.push(
// ...textResults.map(
// (searchResult) =>
// ({
// type: SuggestionType.TEXT,
// value: searchResult,
// label: searchResult.word,
// } as Suggestion)
// )
// );
// return options;
// };
// const getOptions = debounce(getAutoCompleteSuggestions, 250);
// const search = (selectedOption: Suggestion) => {
// // addLogLine('search...');
// if (!selectedOption) {
// return;
// }
// switch (selectedOption.type) {
// case SuggestionType.DATE:
// props.setSearch({
// date: selectedOption.value as DateValue,
// });
// props.setOpen(true);
// break;
// case SuggestionType.LOCATION:
// props.setSearch({
// location: selectedOption.value as Bbox,
// });
// props.setOpen(true);
// break;
// case SuggestionType.COLLECTION:
// props.setActiveCollection(selectedOption.value as number);
// setValue(null);
// break;
// case SuggestionType.IMAGE:
// case SuggestionType.VIDEO:
// props.setSearch({ fileIndex: selectedOption.value as number });
// setValue(null);
// break;
// case SuggestionType.PERSON:
// props.setSearch({ person: selectedOption.value as Person });
// props.setOpen(true);
// break;
// case SuggestionType.THING:
// props.setSearch({ thing: selectedOption.value as Thing });
// props.setOpen(true);
// break;
// case SuggestionType.TEXT:
// props.setSearch({ text: selectedOption.value as WordGroup });
// props.setOpen(true);
// break;
// }
// };
// const resetSearch = () => {
// if (props.isOpen) {
// galleryContext.startLoading();
// props.setSearch({});
// setTimeout(() => {
// galleryContext.finishLoading();
// }, 10);
// props.setOpen(false);
// setValue(null);
// }
// };
// // = =========================
// // UI
// // = =========================
// const getIconByType = (type: SuggestionType) => {
// switch (type) {
// case SuggestionType.DATE:
// return <DateIcon />;
// case SuggestionType.LOCATION:
// return <LocationIcon />;
// case SuggestionType.COLLECTION:
// return <CollectionIcon />;
// case SuggestionType.IMAGE:
// return <ImageIcon />;
// case SuggestionType.VIDEO:
// return <VideoIcon />;
// case SuggestionType.THING:
// return <ObjectIcon />;
// case SuggestionType.TEXT:
// return <TextIcon />;
// default:
// return <SearchIcon />;
// }
// };
// const LabelWithIcon = (props: { type: SuggestionType; label: string }) => (
// <div style={{ display: 'flex', alignItems: 'center' }}>
// <span style={{ paddingRight: '10px', paddingBottom: '4px' }}>
// {getIconByType(props.type)}
// </span>
// <span>{props.label}</span>
// </div>
// );
// const { Option, Control, Menu } = components;
// const OptionWithIcon = (props) =>
// !props.data.hide && (
// <Option {...props}>
// <LabelWithIcon
// type={props.data.type}
// label={props.data.label}
// />
// </Option>
// );
// const ControlWithIcon = (props) => (
// <Control {...props}>
// <span
// className="icon"
// style={{
// paddingLeft: '10px',
// paddingBottom: '4px',
// }}>
// {getIconByType(props.getValue()[0]?.type)}
// </span>
// {props.children}
// </Control>
// );
// const CustomMenu = (props) => {
// // addLogLine("props.selectProps.options: ", selectRef);
// const peopleSuggestions = props.selectProps.options.filter(
// (o) => o.type === SuggestionType.PERSON
// );
// const people = peopleSuggestions.map((o) => o.value);
// const indexStatusSuggestion = props.selectProps.options.filter(
// (o) => o.type === SuggestionType.INDEX_STATUS
// )[0] as Suggestion;
// const indexStatus = indexStatusSuggestion?.value as IndexStatus;
// return (
// <Menu {...props}>
// {appContext.mlSearchEnabled && (
// <Col>
// <LegendRow>
// <Legend>{constants.PEOPLE}</Legend>
// {indexStatus && (
// <Caption>{indexStatusSuggestion.label}</Caption>
// )}
// </LegendRow>
// {people && people.length > 0 && (
// <Row>
// <PeopleList
// people={people}
// maxRows={2}
// onSelect={(person, index) => {
// selectRef.current.blur();
// setValue(peopleSuggestions[index]);
// }}></PeopleList>
// </Row>
// )}
// </Col>
// )}
// {props.children}
// </Menu>
// );
// };
// const customStyles = {
// control: (style, { isFocused }) => ({
// ...style,
// backgroundColor: '#282828',
// color: '#d1d1d1',
// borderColor: isFocused ? '#51cd7c' : '#444',
// boxShadow: 'none',
// ':hover': {
// borderColor: '#51cd7c',
// cursor: 'text',
// '&>.icon': { color: '#51cd7c' },
// },
// }),
// input: (style) => ({
// ...style,
// color: '#d1d1d1',
// }),
// menu: (style) => ({
// ...style,
// marginTop: '10px',
// backgroundColor: '#282828',
// }),
// option: (style, { isFocused }) => ({
// ...style,
// backgroundColor: isFocused && '#343434',
// }),
// dropdownIndicator: (style) => ({
// ...style,
// display: 'none',
// }),
// indicatorSeparator: (style) => ({
// ...style,
// display: 'none',
// }),
// clearIndicator: (style) => ({
// ...style,
// display: 'none',
// }),
// singleValue: (style, state) => ({
// ...style,
// backgroundColor: '#282828',
// color: '#d1d1d1',
// display: state.selectProps.menuIsOpen ? 'none' : 'block',
// }),
// placeholder: (style) => ({
// ...style,
// color: '#686868',
// wordSpacing: '2px',
// whiteSpace: 'nowrap',
// }),
// };
// return (
// <>
// {props.searchStats && (
// <SearchStatsContainer>
// {constants.SEARCH_STATS(props.searchStats)}
// </SearchStatsContainer>
// )}
// <Wrapper isDisabled={props.isFirstFetch} isOpen={props.isOpen}>
// <SearchInput>
// <div
// style={{
// flex: 1,
// margin: '10px',
// }}>
// <AsyncSelect
// ref={selectRef}
// value={value}
// components={{
// Menu: CustomMenu,
// Option: OptionWithIcon,
// Control: ControlWithIcon,
// }}
// placeholder={constants.SEARCH_HINT()}
// loadOptions={getOptions}
// onChange={handleChange}
// onFocus={handleOnFocus}
// isClearable
// escapeClearsValue
// styles={customStyles}
// noOptionsMessage={() => null}
// />
// </div>
// {props.isOpen && (
// <IconButton onClick={() => resetSearch()}>
// <CloseIcon />
// </IconButton>
// )}
// </SearchInput>
// </Wrapper>
// <SearchButton
// isOpen={props.isOpen}
// onClick={() => !props.isFirstFetch && props.setOpen(true)}>
// <SearchIcon />
// </SearchButton>
// </>
// );
// }

View file

@ -1,11 +1,11 @@
import React, { useState } from 'react';
import constants from 'utils/strings/constants';
import { Formik } from 'formik';
import * as Yup from 'yup';
import SubmitButton from './SubmitButton';
import { Box, Input, TextField, Typography } from '@mui/material';
import { PasswordStrengthHint } from './PasswordStrength';
import { isWeakPassword } from 'utils/crypto';
import { Trans, useTranslation } from 'react-i18next';
export interface SetPasswordFormProps {
userEmail: string;
@ -24,6 +24,7 @@ export interface SetPasswordFormValues {
confirm: string;
}
function SetPasswordForm(props: SetPasswordFormProps) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const onSubmit = async (
@ -43,10 +44,10 @@ function SetPasswordForm(props: SetPasswordFormProps) {
if (passphrase === confirm) {
await props.callback(passphrase, setFieldError);
} else {
setFieldError('confirm', constants.PASSPHRASE_MATCH_ERROR);
setFieldError('confirm', t('PASSPHRASE_MATCH_ERROR'));
}
} catch (e) {
setFieldError('confirm', `${constants.UNKNOWN_ERROR} ${e.message}`);
setFieldError('confirm', `${t('UNKNOWN_ERROR} ${e.message}')}`);
} finally {
setLoading(false);
}
@ -56,8 +57,8 @@ function SetPasswordForm(props: SetPasswordFormProps) {
<Formik<SetPasswordFormValues>
initialValues={{ passphrase: '', confirm: '' }}
validationSchema={Yup.object().shape({
passphrase: Yup.string().required(constants.REQUIRED),
confirm: Yup.string().required(constants.REQUIRED),
passphrase: Yup.string().required(t('REQUIRED')),
confirm: Yup.string().required(t('REQUIRED')),
})}
validateOnChange={false}
validateOnBlur={false}
@ -65,7 +66,7 @@ function SetPasswordForm(props: SetPasswordFormProps) {
{({ values, errors, handleChange, handleSubmit }) => (
<form noValidate onSubmit={handleSubmit}>
<Typography mb={2} color="text.secondary" variant="body2">
{constants.ENTER_ENC_PASSPHRASE}
{t('ENTER_ENC_PASSPHRASE')}
</Typography>
<Input
@ -82,7 +83,7 @@ function SetPasswordForm(props: SetPasswordFormProps) {
id="password"
autoComplete="new-password"
type="password"
label={constants.PASSPHRASE_HINT}
label={t('PASSPHRASE_HINT')}
value={values.passphrase}
onChange={handleChange('passphrase')}
error={Boolean(errors.passphrase)}
@ -96,7 +97,7 @@ function SetPasswordForm(props: SetPasswordFormProps) {
id="confirm-password"
autoComplete="new-password"
type="password"
label={constants.CONFIRM_PASSPHRASE}
label={t('CONFIRM_PASSPHRASE')}
value={values.confirm}
onChange={handleChange('confirm')}
disabled={loading}
@ -106,7 +107,11 @@ function SetPasswordForm(props: SetPasswordFormProps) {
<PasswordStrengthHint password={values.passphrase} />
<Typography my={2} variant="body2">
{constants.PASSPHRASE_DISCLAIMER()}
<Trans>
We don't store your password, so if you forget it,{' '}
<strong>we will not be able to help you </strong>
recover your data without a recovery key.
</Trans>
</Typography>
<Box my={4}>
@ -122,7 +127,7 @@ function SetPasswordForm(props: SetPasswordFormProps) {
mt={1}
color="text.secondary"
variant="body2">
{constants.KEY_GENERATION_IN_PROGRESS_MESSAGE}
{t('KEY_GENERATION_IN_PROGRESS_MESSAGE')}
</Typography>
)}
</Box>

View file

@ -1,455 +0,0 @@
export {};
// import React, { useContext, useEffect, useState } from 'react';
// import { slide as Menu } from 'react-burger-menu';
// import constants from 'utils/strings/constants';
// import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
// import { getToken } from 'utils/common/key';
// import { getEndpoint } from 'utils/common/apiUtil';
// import { Button } from 'react-bootstrap';
// import {
// isSubscriptionActive,
// getUserSubscription,
// isOnFreePlan,
// isSubscriptionCancelled,
// isSubscribed,
// convertToHumanReadable,
// } from 'utils/billing';
// import isElectron from 'is-electron';
// import { Collection } from 'types/collection';
// import { useRouter } from 'next/router';
// import LinkButton from './pages/gallery/LinkButton';
// import { downloadApp } from 'utils/common';
// import { getUserDetails, logoutUser } from 'services/userService';
// import { LogoImage } from 'pages/_app';
// import { SetDialogMessage } from './MessageDialog';
// import EnteSpinner from './EnteSpinner';
// import RecoveryKeyModal from './RecoveryKeyModal';
// import TwoFactorModal from './TwoFactorModal';
// import ExportModal from './ExportModal';
// import { GalleryContext } from 'pages/gallery';
// import InProgressIcon from './icons/InProgressIcon';
// import exportService from 'services/exportService';
// import { Subscription } from 'types/billing';
// import { PAGES } from 'constants/pages';
// import { ARCHIVE_SECTION, TRASH_SECTION } from 'constants/collection';
// import FixLargeThumbnails from './FixLargeThumbnail';
// import { AppContext } from 'pages/_app';
// import { canEnableMlSearch } from 'utils/machineLearning/compatibility';
// import { SetLoading } from 'types/gallery';
// import mlIDbStorage from 'utils/storage/mlIDbStorage';
// interface Props {
// collections: Collection[];
// setDialogMessage: SetDialogMessage;
// setLoading: SetLoading;
// }
// export default function Sidebar(props: Props) {
// const [usage, SetUsage] = useState<string>(null);
// const [user, setUser] = useState(null);
// const [subscription, setSubscription] = useState<Subscription>(null);
// useEffect(() => {
// setUser(getData(LS_KEYS.USER));
// setSubscription(getUserSubscription());
// }, []);
// const [isOpen, setIsOpen] = useState(false);
// const [recoverModalView, setRecoveryModalView] = useState(false);
// const [twoFactorModalView, setTwoFactorModalView] = useState(false);
// const [exportModalView, setExportModalView] = useState(false);
// const [fixLargeThumbsView, setFixLargeThumbsView] = useState(false);
// const galleryContext = useContext(GalleryContext);
// const appContext = useContext(AppContext);
// const enableMlSearch = async () => {
// await appContext.updateMlSearchEnabled(true);
// };
// const disableMlSearch = async () => {
// await appContext.updateMlSearchEnabled(false);
// };
// const clearMLDB = async () => {
// await mlIDbStorage.clearMLDB();
// };
// useEffect(() => {
// const main = async () => {
// if (!isOpen) {
// return;
// }
// const userDetails = await getUserDetails();
// setUser({ ...user, email: userDetails.email });
// SetUsage(convertToHumanReadable(userDetails.usage));
// setSubscription(userDetails.subscription);
// setData(LS_KEYS.USER, {
// ...getData(LS_KEYS.USER),
// email: userDetails.email,
// });
// setData(LS_KEYS.SUBSCRIPTION, userDetails.subscription);
// };
// main();
// }, [isOpen]);
// function openFeedbackURL() {
// const feedbackURL: string = `${getEndpoint()}/users/feedback?token=${encodeURIComponent(
// getToken()
// )}`;
// const win = window.open(feedbackURL, '_blank');
// win.focus();
// }
// function initiateEmail(email: string) {
// const a = document.createElement('a');
// a.href = 'mailto:' + email;
// a.rel = 'noreferrer noopener';
// a.click();
// }
// // eslint-disable-next-line @typescript-eslint/no-unused-vars
// function exportFiles() {
// if (isElectron()) {
// setExportModalView(true);
// } else {
// props.setDialogMessage({
// title: constants.DOWNLOAD_APP,
// content: constants.DOWNLOAD_APP_MESSAGE(),
// staticBackdrop: true,
// proceed: {
// text: constants.DOWNLOAD,
// action: downloadApp,
// variant: 'success',
// },
// close: {
// text: constants.CLOSE,
// },
// });
// }
// }
// const router = useRouter();
// function onManageClick() {
// setIsOpen(false);
// galleryContext.showPlanSelectorModal();
// }
// const Divider = () => (
// <div
// style={{
// height: '1px',
// marginTop: '40px',
// background: '#242424',
// width: '100%',
// }}
// />
// );
// return (
// <Menu
// isOpen={isOpen}
// onStateChange={(state) => setIsOpen(state.isOpen)}
// itemListElement="div">
// <div
// style={{
// display: 'flex',
// outline: 'none',
// textAlign: 'center',
// }}>
// <LogoImage
// style={{ height: '24px', padding: '3px' }}
// alt="logo"
// src="/icon.svg"
// />
// </div>
// <div
// style={{
// outline: 'none',
// color: 'rgb(45, 194, 98)',
// fontSize: '16px',
// }}>
// {user?.email}
// </div>
// <div
// style={{
// flex: 1,
// overflow: 'auto',
// outline: 'none',
// paddingTop: '0',
// }}>
// <div style={{ outline: 'none' }}>
// <div style={{ display: 'flex' }}>
// <h5 style={{ margin: '4px 0 12px 2px' }}>
// {constants.SUBSCRIPTION_PLAN}
// </h5>
// </div>
// <div style={{ color: '#959595' }}>
// {isSubscriptionActive(subscription) ? (
// isOnFreePlan(subscription) ? (
// constants.FREE_SUBSCRIPTION_INFO(
// subscription?.expiryTime
// )
// ) : isSubscriptionCancelled(subscription) ? (
// constants.RENEWAL_CANCELLED_SUBSCRIPTION_INFO(
// subscription?.expiryTime
// )
// ) : (
// constants.RENEWAL_ACTIVE_SUBSCRIPTION_INFO(
// subscription?.expiryTime
// )
// )
// ) : (
// <p>{constants.SUBSCRIPTION_EXPIRED}</p>
// )}
// <Button
// variant="outline-success"
// block
// size="sm"
// onClick={onManageClick}>
// {isSubscribed(subscription)
// ? constants.MANAGE
// : constants.SUBSCRIBE}
// </Button>
// </div>
// </div>
// <div style={{ outline: 'none', marginTop: '30px' }} />
// <div>
// <h5 style={{ marginBottom: '12px' }}>
// {constants.USAGE_DETAILS}
// </h5>
// <div style={{ color: '#959595' }}>
// {usage ? (
// constants.USAGE_INFO(
// usage,
// convertToHumanReadable(subscription?.storage)
// )
// ) : (
// <div style={{ textAlign: 'center' }}>
// <EnteSpinner
// style={{
// borderWidth: '2px',
// width: '20px',
// height: '20px',
// }}
// />
// </div>
// )}
// </div>
// </div>
// <Divider />
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => {
// galleryContext.setActiveCollection(ARCHIVE_SECTION);
// setIsOpen(false);
// }}>
// {constants.ARCHIVE}
// </LinkButton>
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => {
// galleryContext.setActiveCollection(TRASH_SECTION);
// setIsOpen(false);
// }}>
// {constants.TRASH}
// </LinkButton>
// <>
// <RecoveryKeyModal
// show={recoverModalView}
// onHide={() => setRecoveryModalView(false)}
// somethingWentWrong={() =>
// props.setDialogMessage({
// title: constants.ERROR,
// content:
// constants.RECOVER_KEY_GENERATION_FAILED,
// close: { variant: 'danger' },
// })
// }
// />
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => setRecoveryModalView(true)}>
// {constants.DOWNLOAD_RECOVERY_KEY}
// </LinkButton>
// </>
// <>
// <TwoFactorModal
// show={twoFactorModalView}
// onHide={() => setTwoFactorModalView(false)}
// setDialogMessage={props.setDialogMessage}
// closeSidebar={() => setIsOpen(false)}
// setLoading={props.setLoading}
// />
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => setTwoFactorModalView(true)}>
// {constants.TWO_FACTOR}
// </LinkButton>
// </>
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => {
// router.push(PAGES.CHANGE_PASSWORD);
// }}>
// {constants.CHANGE_PASSWORD}
// </LinkButton>
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => {
// router.push(PAGES.CHANGE_EMAIL);
// }}>
// {constants.UPDATE_EMAIL}
// </LinkButton>
// <Divider />
// <>
// <FixLargeThumbnails
// isOpen={fixLargeThumbsView}
// hide={() => setFixLargeThumbsView(false)}
// show={() => setFixLargeThumbsView(true)}
// />
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => setFixLargeThumbsView(true)}>
// {constants.FIX_LARGE_THUMBNAILS}
// </LinkButton>
// </>
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={openFeedbackURL}>
// {constants.REQUEST_FEATURE}
// </LinkButton>
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => {
// if (!appContext.mlSearchEnabled) {
// if (!canEnableMlSearch()) {
// props.setDialogMessage({
// title: constants.ENABLE_ML_SEARCH,
// content: constants.ML_SEARCH_NOT_COMPATIBLE,
// close: { text: constants.OK },
// });
// return;
// }
// props.setDialogMessage({
// title: `${constants.CONFIRM} ${constants.ENABLE_ML_SEARCH}`,
// content: constants.ENABLE_ML_SEARCH_MESSAGE,
// staticBackdrop: true,
// proceed: {
// text: constants.ENABLE_ML_SEARCH,
// action: enableMlSearch,
// variant: 'success',
// },
// close: { text: constants.CANCEL },
// });
// } else {
// disableMlSearch();
// }
// }}>
// {appContext.mlSearchEnabled
// ? constants.DISABLE_ML_SEARCH
// : constants.ENABLE_ML_SEARCH}
// </LinkButton>
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => {
// if (!appContext.mlSearchEnabled) {
// if (!canEnableMlSearch()) {
// props.setDialogMessage({
// title: constants.ENABLE_ML_SEARCH,
// content: constants.ML_SEARCH_NOT_COMPATIBLE,
// close: { text: constants.OK },
// });
// return;
// }
// props.setDialogMessage({
// title: 'clear mb db',
// content: 'clear mb db',
// staticBackdrop: true,
// proceed: {
// text: 'clear',
// action: clearMLDB,
// variant: 'success',
// },
// close: { text: constants.CANCEL },
// });
// } else {
// disableMlSearch();
// }
// }}>
// {'clear ML db'}
// </LinkButton>
// {appContext.mlSearchEnabled && (
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => {
// router.push(PAGES.ML_DEBUG);
// }}>
// {constants.ML_DEBUG}
// </LinkButton>
// )}
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={() => initiateEmail('contact@ente.io')}>
// {constants.SUPPORT}
// </LinkButton>
// <>
// <ExportModal
// show={exportModalView}
// onHide={() => setExportModalView(false)}
// usage={usage}
// />
// <LinkButton
// style={{ marginTop: '30px' }}
// onClick={exportFiles}>
// <div style={{ display: 'flex' }}>
// {constants.EXPORT}
// <div style={{ width: '20px' }} />
// {exportService.isExportInProgress() && (
// <InProgressIcon />
// )}
// </div>
// </LinkButton>
// </>
// <Divider />
// <LinkButton
// variant="danger"
// style={{ marginTop: '30px' }}
// onClick={() =>
// props.setDialogMessage({
// title: `${constants.CONFIRM} ${constants.LOGOUT}`,
// content: constants.LOGOUT_MESSAGE,
// staticBackdrop: true,
// proceed: {
// text: constants.LOGOUT,
// action: logoutUser,
// variant: 'danger',
// },
// close: { text: constants.CANCEL },
// })
// }>
// {constants.LOGOUT}
// </LinkButton>
// <LinkButton
// variant="danger"
// style={{ marginTop: '30px' }}
// onClick={() =>
// props.setDialogMessage({
// title: `${constants.DELETE_ACCOUNT}`,
// content: constants.DELETE_ACCOUNT_MESSAGE(),
// staticBackdrop: true,
// proceed: {
// text: constants.DELETE_ACCOUNT,
// action: () => {
// initiateEmail('account-deletion@ente.io');
// },
// variant: 'danger',
// },
// close: { text: constants.CANCEL },
// })
// }>
// {constants.DELETE_ACCOUNT}
// </LinkButton>
// <div
// style={{
// marginTop: '40px',
// width: '100%',
// }}
// />
// </div>
// </Menu>
// );
// }

View file

@ -6,10 +6,13 @@ import MLSearchSettings from 'components/MachineLearning/MLSearchSettings';
import MenuSectionTitle from 'components/Menu/MenuSectionTitle';
import Titlebar from 'components/Titlebar';
import { useState } from 'react';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import SidebarButton from './Button';
export default function AdvancedSettings({ open, onClose, onRootClose }) {
const { t } = useTranslation();
const [mlSearchSettingsView, setMlSearchSettingsView] = useState(false);
const openMlSearchSettings = () => setMlSearchSettingsView(true);
@ -39,7 +42,7 @@ export default function AdvancedSettings({ open, onClose, onRootClose }) {
<Stack spacing={'4px'} py={'12px'}>
<Titlebar
onClose={onClose}
title={constants.ADVANCED}
title={t('ADVANCED')}
onRootClose={handleRootClose}
/>
@ -47,7 +50,7 @@ export default function AdvancedSettings({ open, onClose, onRootClose }) {
<Stack py="20px" spacing="24px">
<Box>
<MenuSectionTitle
title={constants.LABS}
title={t('LABS')}
icon={<ScienceIcon />}
/>
<SidebarButton
@ -55,7 +58,7 @@ export default function AdvancedSettings({ open, onClose, onRootClose }) {
color="secondary"
endIcon={<ChevronRight />}
onClick={openMlSearchSettings}>
{constants.ML_SEARCH}
{t('ML_SEARCH')}
</SidebarButton>
</Box>
</Stack>

View file

@ -1,7 +1,8 @@
import { AppContext } from 'pages/_app';
import React, { useContext, useEffect, useState } from 'react';
import { downloadAsFile } from 'utils/file';
import constants from 'utils/strings/constants';
import { Trans, useTranslation } from 'react-i18next';
import { addLogLine, getDebugLogs } from 'utils/logging';
import SidebarButton from './Button';
import isElectron from 'is-electron';
@ -15,6 +16,8 @@ import {
} from '../../../tests/zip-file-reading.test';
export default function DebugSection() {
const { t } = useTranslation();
const appContext = useContext(AppContext);
const [appVersion, setAppVersion] = useState<string>(null);
@ -30,15 +33,26 @@ export default function DebugSection() {
const confirmLogDownload = () =>
appContext.setDialogMessage({
title: constants.DOWNLOAD_LOGS,
content: constants.DOWNLOAD_LOGS_MESSAGE(),
title: t('DOWNLOAD_LOGS'),
content: (
<Trans i18nKey={'DOWNLOAD_LOGS_MESSAGE'}>
<p>
This will download debug logs, which you can email to us
to help debug your issue.
</p>
<p>
Please note that file names will be included to help
track issues with specific files.
</p>
</Trans>
),
proceed: {
text: constants.DOWNLOAD,
text: t('DOWNLOAD'),
variant: 'accent',
action: downloadDebugLogs,
},
close: {
text: constants.CANCEL,
text: t('CANCEL'),
},
});
@ -59,7 +73,7 @@ export default function DebugSection() {
onClick={confirmLogDownload}
typographyVariant="caption"
sx={{ fontWeight: 'normal', color: 'text.secondary' }}>
{constants.DOWNLOAD_UPLOAD_LOGS}
{t('DOWNLOAD_UPLOAD_LOGS')}
</SidebarButton>
{appVersion && (
<Typography p={1.5} color="text.secondary" variant="caption">

View file

@ -1,11 +1,13 @@
import React, { useContext, useState } from 'react';
import SidebarButton from './Button';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import { logoutUser } from 'services/userService';
import { AppContext } from 'pages/_app';
import DeleteAccountModal from 'components/DeleteAccountModal';
export default function ExitSection() {
const { t } = useTranslation();
const { setDialogMessage } = useContext(AppContext);
const [deleteAccountModalView, setDeleteAccountModalView] = useState(false);
@ -15,23 +17,23 @@ export default function ExitSection() {
const confirmLogout = () => {
setDialogMessage({
title: constants.LOGOUT_MESSAGE,
title: t('LOGOUT_MESSAGE'),
proceed: {
text: constants.LOGOUT,
text: t('LOGOUT'),
action: logoutUser,
variant: 'danger',
},
close: { text: constants.CANCEL },
close: { text: t('CANCEL') },
});
};
return (
<>
<SidebarButton onClick={confirmLogout} color="danger">
{constants.LOGOUT}
{t('LOGOUT')}
</SidebarButton>
<SidebarButton onClick={openDeleteAccountModal} color="danger">
{constants.DELETE_ACCOUNT}
{t('DELETE_ACCOUNT')}
</SidebarButton>
<DeleteAccountModal
open={deleteAccountModalView}

View file

@ -1,6 +1,7 @@
import React, { useContext, useState } from 'react';
import SidebarButton from './Button';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import ExportModal from 'components/ExportModal';
import exportService from 'services/exportService';
import { getEndpoint } from 'utils/common/apiUtil';
@ -13,6 +14,7 @@ import { NoStyleAnchor } from 'components/pages/sharedAlbum/GoToEnte';
import { openLink } from 'utils/common';
export default function HelpSection() {
const { t } = useTranslation();
const [exportModalView, setExportModalView] = useState(false);
const { setDialogMessage } = useContext(AppContext);
@ -35,16 +37,16 @@ export default function HelpSection() {
return (
<>
<SidebarButton onClick={openFeedbackURL}>
{constants.REQUEST_FEATURE}
{t('REQUEST_FEATURE')}
</SidebarButton>
<SidebarButton
LinkComponent={NoStyleAnchor}
href="mailto:contact@ente.io">
{constants.SUPPORT}
{t('SUPPORT')}
</SidebarButton>
<SidebarButton onClick={exportFiles}>
<div style={{ display: 'flex' }}>
{constants.EXPORT}
{t('EXPORT')}
<div style={{ width: '20px' }} />
{exportService.isExportInProgress() && (
<EnteSpinner size="20px" />

View file

@ -5,12 +5,14 @@ import MenuSectionTitle from 'components/Menu/MenuSectionTitle';
import Titlebar from 'components/Titlebar';
import isElectron from 'is-electron';
import { useState } from 'react';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import AdvancedSettings from '../AdvancedSettings';
import SidebarButton from '../Button';
import { LanguageSelector } from './LanguageSelector';
export default function Preferences({ open, onClose, onRootClose }) {
const { t } = useTranslation();
const [advancedSettingsView, setAdvancedSettingsView] = useState(false);
const openAdvancedSettings = () => setAdvancedSettingsView(true);
@ -40,13 +42,13 @@ export default function Preferences({ open, onClose, onRootClose }) {
<Stack spacing={'4px'} py={'12px'}>
<Titlebar
onClose={onClose}
title={constants.PREFERENCES}
title={t('PREFERENCES')}
onRootClose={handleRootClose}
/>
<Box px={'8px'}>
<Stack py="20px" spacing="24px">
<Box>
<MenuSectionTitle title={constants.LANGUAGE} />
<MenuSectionTitle title={t('LANGUAGE')} />
<LanguageSelector />
</Box>
{isElectron() && (
@ -55,7 +57,7 @@ export default function Preferences({ open, onClose, onRootClose }) {
color="secondary"
onClick={openAdvancedSettings}
endIcon={<ChevronRight />}>
{constants.ADVANCED}
{t('ADVANCED')}
</SidebarButton>
)}
</Stack>

View file

@ -1,5 +1,6 @@
import React, { useContext, useState, useEffect } from 'react';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import { GalleryContext } from 'pages/gallery';
import {
ARCHIVE_SECTION,
@ -21,6 +22,8 @@ export default function ShortcutSection({
closeSidebar,
collectionSummaries,
}: Iprops) {
const { t } = useTranslation();
const galleryContext = useContext(GalleryContext);
const [uncategorizedCollectionId, setUncategorizedCollectionID] =
useState<number>();
@ -54,7 +57,7 @@ export default function ShortcutSection({
<>
<ShortcutButton
startIcon={<CategoryIcon />}
label={constants.UNCATEGORIZED}
label={t('UNCATEGORIZED')}
onClick={openUncategorizedSection}
count={
collectionSummaries.get(uncategorizedCollectionId)
@ -63,13 +66,13 @@ export default function ShortcutSection({
/>
<ShortcutButton
startIcon={<DeleteOutline />}
label={constants.TRASH}
label={t('TRASH')}
count={collectionSummaries.get(TRASH_SECTION)?.fileCount}
onClick={openTrashSection}
/>
<ShortcutButton
startIcon={<ArchiveOutlined />}
label={constants.ARCHIVE_SECTION_NAME}
label={t('ARCHIVE_SECTION_NAME')}
count={collectionSummaries.get(ARCHIVE_SECTION)?.fileCount}
onClick={openArchiveSection}
/>

View file

@ -3,7 +3,7 @@ import { FamilyUsageProgressBar } from './progressBar';
import { Box, Stack, Typography } from '@mui/material';
import { SpaceBetweenFlex } from 'components/Container';
import React from 'react';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
interface Iprops {
userUsage: number;
@ -18,6 +18,7 @@ export function FamilyUsageSection({
fileCount,
totalStorage,
}: Iprops) {
const { t } = useTranslation();
return (
<Box width="100%">
<FamilyUsageProgressBar
@ -30,11 +31,11 @@ export function FamilyUsageSection({
marginTop: 1.5,
}}>
<Stack direction={'row'} spacing={1.5}>
<Legend label={constants.YOU} color="text.primary" />
<Legend label={constants.FAMILY} color="text.secondary" />
<Legend label={t('YOU')} color="text.primary" />
<Legend label={t('FAMILY')} color="text.secondary" />
</Stack>
<Typography variant="caption" fontWeight={'bold'}>
{constants.PHOTO_COUNT(fileCount ?? 0)}
{t('PHOTO_COUNT', { count: fileCount ?? 0 })}
</Typography>
</SpaceBetweenFlex>
</Box>

View file

@ -2,7 +2,8 @@ import { Box, Typography } from '@mui/material';
import { SpaceBetweenFlex } from 'components/Container';
import React from 'react';
import { makeHumanReadableStorage } from 'utils/billing';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import { Progressbar } from '../../styledComponents';
interface Iprops {
@ -11,6 +12,8 @@ interface Iprops {
storage: number;
}
export function IndividualUsageSection({ usage, storage, fileCount }: Iprops) {
const { t } = useTranslation();
return (
<Box width="100%">
<Progressbar value={Math.min((usage * 100) / storage, 100)} />
@ -20,9 +23,9 @@ export function IndividualUsageSection({ usage, storage, fileCount }: Iprops) {
}}>
<Typography variant="caption">{`${makeHumanReadableStorage(
storage - usage
)} ${constants.FREE}`}</Typography>
)} ${t('FREE')}`}</Typography>
<Typography variant="caption" fontWeight={'bold'}>
{constants.PHOTO_COUNT(fileCount ?? 0)}
{t('PHOTO_COUNT', { count: fileCount ?? 0 })}
</Typography>
</SpaceBetweenFlex>
</Box>

View file

@ -1,7 +1,7 @@
import { Box, styled, Typography } from '@mui/material';
import React from 'react';
import { convertBytesToGBs, makeHumanReadableStorage } from 'utils/billing';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
const MobileSmallBox = styled(Box)`
display: none;
@ -21,18 +21,20 @@ interface Iprops {
storage: number;
}
export default function StorageSection({ usage, storage }: Iprops) {
const { t } = useTranslation();
return (
<Box width="100%">
<Typography variant="body2" color={'text.secondary'}>
{constants.STORAGE}
{t('STORAGE')}
</Typography>
<DefaultBox>
<Typography
fontWeight={'bold'}
sx={{ fontSize: '24px', lineHeight: '30px' }}>
{`${makeHumanReadableStorage(usage, { roundUp: true })} ${
constants.OF
} ${makeHumanReadableStorage(storage)} ${constants.USED}`}
{`${makeHumanReadableStorage(usage, { roundUp: true })} ${t(
'OF'
)} ${makeHumanReadableStorage(storage)} ${t('USED')}`}
</Typography>
</DefaultBox>
<MobileSmallBox>
@ -41,7 +43,7 @@ export default function StorageSection({ usage, storage }: Iprops) {
sx={{ fontSize: '24px', lineHeight: '30px' }}>
{`${convertBytesToGBs(usage)} / ${convertBytesToGBs(
storage
)} ${constants.GB} ${constants.USED}`}
)} ${t('GB')} ${t('USED')}`}
</Typography>
</MobileSmallBox>
</Box>

View file

@ -10,7 +10,8 @@ import {
} from 'utils/billing';
import Box from '@mui/material/Box';
import { UserDetails } from 'types/user';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import { Typography } from '@mui/material';
import billingService from 'services/billingService';
import { isPartOfFamily, isFamilyAdmin } from 'utils/user/family';
@ -20,6 +21,8 @@ export default function SubscriptionStatus({
}: {
userDetails: UserDetails;
}) {
const { t } = useTranslation();
const { showPlanSelectorModal } = useContext(GalleryContext);
const hasAMessage = useMemo(() => {
@ -74,18 +77,22 @@ export default function SubscriptionStatus({
sx={{ cursor: handleClick && 'pointer' }}>
{isSubscriptionActive(userDetails.subscription)
? isOnFreePlan(userDetails.subscription)
? constants.FREE_SUBSCRIPTION_INFO(
userDetails.subscription?.expiryTime
)
? t('FREE_SUBSCRIPTION_INFO', {
data: new Date(
userDetails.subscription?.expiryTime
),
})
: isSubscriptionCancelled(userDetails.subscription)
? constants.RENEWAL_CANCELLED_SUBSCRIPTION_INFO(
userDetails.subscription?.expiryTime
)
? t('RENEWAL_CANCELLED_SUBSCRIPTION_INFO', {
data: new Date(
userDetails.subscription?.expiryTime
),
})
: hasExceededStorageQuota(userDetails) &&
constants.STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO(
handleClick
)
: constants.SUBSCRIPTION_EXPIRED_MESSAGE(handleClick)}
t('STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO', {
handleClick,
})
: t('SUBSCRIPTION_EXPIRED_MESSAGE', { handleClick })}
</Typography>
</Box>
);

View file

@ -1,6 +1,7 @@
import React, { useContext, useState } from 'react';
import SidebarButton from './Button';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
// import FixLargeThumbnails from 'components/FixLargeThumbnail';
import RecoveryKey from 'components/RecoveryKey';
import TwoFactorModal from 'components/TwoFactor/Modal';
@ -18,6 +19,8 @@ import { isInternalUser } from 'utils/user';
import Preferences from './Preferences';
export default function UtilitySection({ closeSidebar }) {
const { t } = useTranslation();
const router = useRouter();
const {
setDialogMessage,
@ -64,8 +67,8 @@ export default function UtilitySection({ closeSidebar }) {
const somethingWentWrong = () =>
setDialogMessage({
title: constants.ERROR,
content: constants.RECOVER_KEY_GENERATION_FAILED,
title: t('ERROR'),
content: t('RECOVER_KEY_GENERATION_FAILED'),
close: { variant: 'danger' },
});
@ -73,32 +76,32 @@ export default function UtilitySection({ closeSidebar }) {
<>
{isElectron() && (
<SidebarButton onClick={openWatchFolder}>
{constants.WATCH_FOLDERS}
{t('WATCH_FOLDERS')}
</SidebarButton>
)}
<SidebarButton onClick={openRecoveryKeyModal}>
{constants.RECOVERY_KEY}
{t('RECOVERY_KEY')}
</SidebarButton>
{isInternalUser() && (
<SpaceBetweenFlex sx={{ px: 1.5 }}>
{constants.CHOSE_THEME}
{t('CHOSE_THEME')}
<ThemeSwitcher theme={theme} setTheme={setTheme} />
</SpaceBetweenFlex>
)}
<SidebarButton onClick={openTwoFactorModal}>
{constants.TWO_FACTOR}
{t('TWO_FACTOR')}
</SidebarButton>
<SidebarButton onClick={redirectToChangePasswordPage}>
{constants.CHANGE_PASSWORD}
{t('CHANGE_PASSWORD')}
</SidebarButton>
<SidebarButton onClick={redirectToChangeEmailPage}>
{constants.CHANGE_EMAIL}
{t('CHANGE_EMAIL')}
</SidebarButton>
<SidebarButton onClick={redirectToDeduplicatePage}>
{constants.DEDUPLICATE_FILES}
{t('DEDUPLICATE_FILES')}
</SidebarButton>
<SidebarButton onClick={openPreferencesOptions}>
{constants.PREFERENCES}
{t('PREFERENCES')}
</SidebarButton>
<RecoveryKey

View file

@ -1,5 +1,4 @@
import React, { useState } from 'react';
import constants from 'utils/strings/constants';
import { Formik, FormikHelpers } from 'formik';
import * as Yup from 'yup';
import { sendOtt } from 'services/userService';
@ -21,6 +20,7 @@ import {
Checkbox,
FormControlLabel,
FormGroup,
Link,
TextField,
Typography,
} from '@mui/material';
@ -29,6 +29,7 @@ import LinkButton from './pages/gallery/LinkButton';
import FormPaperFooter from './Form/FormPaper/Footer';
import VerticallyCentered from './Container';
import { PasswordStrengthHint } from './PasswordStrength';
import { Trans, useTranslation } from 'react-i18next';
interface FormValues {
email: string;
@ -41,6 +42,7 @@ interface SignUpProps {
}
export default function SignUp(props: SignUpProps) {
const { t } = useTranslation();
const router = useRouter();
const [acceptTerms, setAcceptTerms] = useState(false);
const [loading, setLoading] = useState(false);
@ -51,7 +53,7 @@ export default function SignUp(props: SignUpProps) {
) => {
try {
if (passphrase !== confirm) {
setFieldError('confirm', constants.PASSPHRASE_MATCH_ERROR);
setFieldError('confirm', t('PASSPHRASE_MATCH_ERROR'));
return;
}
setLoading(true);
@ -59,10 +61,7 @@ export default function SignUp(props: SignUpProps) {
setData(LS_KEYS.USER, { email });
await sendOtt(email);
} catch (e) {
setFieldError(
'confirm',
`${constants.UNKNOWN_ERROR} ${e.message}`
);
setFieldError('confirm', `${t('UNKNOWN_ERROR} ${e.message}')}`);
throw e;
}
try {
@ -82,7 +81,7 @@ export default function SignUp(props: SignUpProps) {
setJustSignedUp(true);
router.push(PAGES.VERIFY);
} catch (e) {
setFieldError('confirm', constants.PASSWORD_GENERATION_FAILED);
setFieldError('confirm', t('PASSWORD_GENERATION_FAILED'));
throw e;
}
} catch (err) {
@ -93,7 +92,7 @@ export default function SignUp(props: SignUpProps) {
return (
<>
<FormPaperTitle> {constants.SIGN_UP}</FormPaperTitle>
<FormPaperTitle> {t('SIGN_UP')}</FormPaperTitle>
<Formik<FormValues>
initialValues={{
email: '',
@ -102,10 +101,10 @@ export default function SignUp(props: SignUpProps) {
}}
validationSchema={Yup.object().shape({
email: Yup.string()
.email(constants.EMAIL_ERROR)
.required(constants.REQUIRED),
passphrase: Yup.string().required(constants.REQUIRED),
confirm: Yup.string().required(constants.REQUIRED),
.email(t('EMAIL_ERROR'))
.required(t('REQUIRED')),
passphrase: Yup.string().required(t('REQUIRED')),
confirm: Yup.string().required(t('REQUIRED')),
})}
validateOnChange={false}
validateOnBlur={false}
@ -124,7 +123,7 @@ export default function SignUp(props: SignUpProps) {
name="email"
autoComplete="username"
type="email"
label={constants.ENTER_EMAIL}
label={t('ENTER_EMAIL')}
value={values.email}
onChange={handleChange('email')}
error={Boolean(errors.email)}
@ -139,7 +138,7 @@ export default function SignUp(props: SignUpProps) {
name="password"
autoComplete="new-password"
type="password"
label={constants.PASSPHRASE_HINT}
label={t('PASSPHRASE_HINT')}
value={values.passphrase}
onChange={handleChange('passphrase')}
error={Boolean(errors.passphrase)}
@ -153,7 +152,7 @@ export default function SignUp(props: SignUpProps) {
name="confirm-password"
autoComplete="new-password"
type="password"
label={constants.CONFIRM_PASSPHRASE}
label={t('CONFIRM_PASSPHRASE')}
value={values.confirm}
onChange={handleChange('confirm')}
error={Boolean(errors.confirm)}
@ -181,14 +180,33 @@ export default function SignUp(props: SignUpProps) {
color="accent"
/>
}
label={constants.TERMS_AND_CONDITIONS()}
label={
<Trans>
<Typography variant="body2">
I agree to the{' '}
<Link
href="https://ente.io/terms"
target="_blank"
rel="noreferrer">
terms
</Link>{' '}
and{' '}
<Link
href="https://ente.io/privacy"
target="_blank"
rel="noreferrer">
privacy policy
</Link>{' '}
</Typography>
</Trans>
}
/>
</FormGroup>
</VerticallyCentered>
<Box my={4}>
<SubmitButton
sx={{ my: 0 }}
buttonText={constants.CREATE_ACCOUNT}
buttonText={t('CREATE_ACCOUNT')}
loading={loading}
disabled={
!acceptTerms ||
@ -201,9 +219,7 @@ export default function SignUp(props: SignUpProps) {
textAlign={'center'}
color="text.secondary"
variant="body2">
{
constants.KEY_GENERATION_IN_PROGRESS_MESSAGE
}
{t('KEY_GENERATION_IN_PROGRESS_MESSAGE')}
</Typography>
)}
</Box>
@ -213,7 +229,7 @@ export default function SignUp(props: SignUpProps) {
<FormPaperFooter>
<LinkButton onClick={props.login}>
{constants.ACCOUNT_EXISTS}
{t('ACCOUNT_EXISTS')}
</LinkButton>
</FormPaperFooter>
</>

View file

@ -1,5 +1,4 @@
import React, { useMemo, useState } from 'react';
import constants from 'utils/strings/constants';
import { Formik, FormikHelpers, FormikState } from 'formik';
import * as Yup from 'yup';
import SubmitButton from './SubmitButton';
@ -7,6 +6,7 @@ import TextField from '@mui/material/TextField';
import ShowHidePassword from './Form/ShowHidePassword';
import { FlexWrapper } from './Container';
import { Button, FormHelperText } from '@mui/material';
import { useTranslation } from 'react-i18next';
interface formValues {
inputValue: string;
@ -33,6 +33,7 @@ export interface SingleInputFormProps {
}
export default function SingleInputForm(props: SingleInputFormProps) {
const { t } = useTranslation();
const { submitButtonProps } = props;
const { sx: buttonSx, ...restSubmitButtonProps } = submitButtonProps ?? {};
@ -66,17 +67,17 @@ export default function SingleInputForm(props: SingleInputFormProps) {
switch (props.fieldType) {
case 'text':
return Yup.object().shape({
inputValue: Yup.string().required(constants.REQUIRED),
inputValue: Yup.string().required(t('REQUIRED')),
});
case 'password':
return Yup.object().shape({
inputValue: Yup.string().required(constants.REQUIRED),
inputValue: Yup.string().required(t('REQUIRED')),
});
case 'email':
return Yup.object().shape({
inputValue: Yup.string()
.email(constants.EMAIL_ERROR)
.required(constants.REQUIRED),
.email(t('EMAIL_ERROR'))
.required(t('REQUIRED')),
});
}
}, [props.fieldType]);
@ -151,7 +152,7 @@ export default function SingleInputForm(props: SingleInputFormProps) {
},
}}
{...restSubmitButtonProps}>
{constants.CANCEL}
{t('CANCEL')}
</Button>
)}
<SubmitButton

View file

@ -1,5 +1,6 @@
import React, { useContext } from 'react';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import { AppContext } from 'pages/_app';
import { PAGES } from 'constants/pages';
import router from 'next/router';
@ -12,18 +13,20 @@ interface Iprops {
}
export default function TwoFactorModalManageSection(props: Iprops) {
const { t } = useTranslation();
const { closeDialog } = props;
const { setDialogMessage } = useContext(AppContext);
const warnTwoFactorDisable = async () => {
setDialogMessage({
title: constants.DISABLE_TWO_FACTOR,
title: t('DISABLE_TWO_FACTOR'),
content: constants.DISABLE_TWO_FACTOR_MESSAGE,
close: { text: constants.CANCEL },
content: t('DISABLE_TWO_FACTOR_MESSAGE'),
close: { text: t('CANCEL') },
proceed: {
variant: 'danger',
text: constants.DISABLE,
text: t('DISABLE'),
action: twoFactorDisable,
},
});
@ -39,7 +42,7 @@ export default function TwoFactorModalManageSection(props: Iprops) {
closeDialog();
} catch (e) {
setDialogMessage({
title: constants.TWO_FACTOR_DISABLE_FAILED,
title: t('TWO_FACTOR_DISABLE_FAILED'),
close: {},
});
}
@ -47,13 +50,13 @@ export default function TwoFactorModalManageSection(props: Iprops) {
const warnTwoFactorReconfigure = async () => {
setDialogMessage({
title: constants.UPDATE_TWO_FACTOR,
title: t('UPDATE_TWO_FACTOR'),
content: constants.UPDATE_TWO_FACTOR_MESSAGE,
close: { text: constants.CANCEL },
content: t('UPDATE_TWO_FACTOR_MESSAGE'),
close: { text: t('CANCEL') },
proceed: {
variant: 'accent',
text: constants.UPDATE,
text: t('UPDATE'),
action: reconfigureTwoFactor,
},
});
@ -73,14 +76,14 @@ export default function TwoFactorModalManageSection(props: Iprops) {
alignItems="center"
justifyContent="center">
<Grid item sm={9} xs={12}>
{constants.UPDATE_TWO_FACTOR_LABEL}
{t('UPDATE_TWO_FACTOR_LABEL')}
</Grid>
<Grid item sm={3} xs={12}>
<Button
color={'accent'}
onClick={warnTwoFactorReconfigure}
size="large">
{constants.RECONFIGURE}
{t('RECONFIGURE')}
</Button>
</Grid>
</Grid>
@ -90,7 +93,7 @@ export default function TwoFactorModalManageSection(props: Iprops) {
alignItems="center"
justifyContent="center">
<Grid item sm={9} xs={12}>
{constants.DISABLE_TWO_FACTOR_LABEL}{' '}
{t('DISABLE_TWO_FACTOR_LABEL')}{' '}
</Grid>
<Grid item sm={3} xs={12}>
@ -98,7 +101,7 @@ export default function TwoFactorModalManageSection(props: Iprops) {
color={'danger'}
onClick={warnTwoFactorDisable}
size="large">
{constants.DISABLE}
{t('DISABLE')}
</Button>
</Grid>
</Grid>

View file

@ -2,7 +2,8 @@ import React from 'react';
import LockIcon from '@mui/icons-material/Lock';
import { PAGES } from 'constants/pages';
import { useRouter } from 'next/router';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import VerticallyCentered from 'components/Container';
import { Button, Typography } from '@mui/material';
@ -11,6 +12,7 @@ interface Iprops {
}
export default function TwoFactorModalSetupSection({ closeDialog }: Iprops) {
const { t } = useTranslation();
const router = useRouter();
const redirectToTwoFactorSetup = () => {
closeDialog();
@ -20,13 +22,13 @@ export default function TwoFactorModalSetupSection({ closeDialog }: Iprops) {
return (
<VerticallyCentered sx={{ mb: 2 }}>
<LockIcon sx={{ fontSize: (theme) => theme.spacing(5), mb: 2 }} />
<Typography mb={4}>{constants.TWO_FACTOR_INFO}</Typography>
<Typography mb={4}>{t('TWO_FACTOR_INFO')}</Typography>
<Button
variant="contained"
color="accent"
size="large"
onClick={redirectToTwoFactorSetup}>
{constants.ENABLE_TWO_FACTOR}
{t('ENABLE_TWO_FACTOR')}
</Button>
</VerticallyCentered>
);

View file

@ -2,7 +2,8 @@ import React, { useEffect, useState } from 'react';
import { getTwoFactorStatus } from 'services/userService';
import { SetLoading } from 'types/gallery';
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import TwoFactorModalSetupSection from './Setup';
import TwoFactorModalManageSection from './Manage';
import { Dialog, DialogContent, styled } from '@mui/material';
@ -21,6 +22,8 @@ interface Props {
}
function TwoFactorModal(props: Props) {
const { t } = useTranslation();
const [isTwoFactorEnabled, setTwoFactorStatus] = useState(false);
useEffect(() => {
@ -52,7 +55,7 @@ function TwoFactorModal(props: Props) {
return (
<TwoFactorDialog maxWidth="xs" open={props.show} onClose={props.onHide}>
<DialogTitleWithCloseButton onClose={props.onHide}>
{constants.TWO_FACTOR_AUTHENTICATION}
{t('TWO_FACTOR_AUTHENTICATION')}
</DialogTitleWithCloseButton>
<DialogContent sx={{ px: 4 }}>
{isTwoFactorEnabled ? (

View file

@ -2,7 +2,8 @@
import { Formik, FormikHelpers } from 'formik';
import React, { FC, useRef, useState } from 'react';
import OtpInput from 'react-otp-input';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import SubmitButton from 'components/SubmitButton';
import VerticallyCentered, { CenteredFlex } from 'components/Container';
import { Box, Typography, TypographyProps } from '@mui/material';
@ -23,6 +24,7 @@ export type VerifyTwoFactorCallback = (
) => Promise<void>;
export default function VerifyTwoFactor(props: Props) {
const { t } = useTranslation();
const [waiting, setWaiting] = useState(false);
const otpInputRef = useRef(null);
const [success, setSuccess] = useState(false);
@ -45,7 +47,7 @@ export default function VerifyTwoFactor(props: Props) {
for (let i = 0; i < 6; i++) {
otpInputRef.current?.focusPrevInput();
}
setFieldError('otp', `${constants.UNKNOWN_ERROR} ${e.message}`);
setFieldError('otp', `${t('UNKNOWN_ERROR')} ${e.message}`);
}
setWaiting(false);
};
@ -70,7 +72,7 @@ export default function VerifyTwoFactor(props: Props) {
mb={2}
variant="body2"
color="text.secondary">
{constants.ENTER_TWO_FACTOR_OTP}
{t('ENTER_TWO_FACTOR_OTP')}
</Typography>
<Box my={2}>
<OtpInput
@ -89,7 +91,7 @@ export default function VerifyTwoFactor(props: Props) {
{errors.otp && (
<CenteredFlex sx={{ mt: 1 }}>
<InvalidInputMessage>
{constants.INCORRECT_CODE}
{t('INCORRECT_CODE')}
</InvalidInputMessage>
</CenteredFlex>
)}

View file

@ -2,7 +2,8 @@ import React from 'react';
import { ButtonProps, IconButton, styled } from '@mui/material';
import FileUploadOutlinedIcon from '@mui/icons-material/FileUploadOutlined';
import { Button } from '@mui/material';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import uploadManager from 'services/upload/uploadManager';
import { UploadTypeSelectorIntent } from 'types/gallery';
@ -41,6 +42,8 @@ function UploadButton({
disableShrink,
icon,
}: Iprops) {
const { t } = useTranslation();
const onClickHandler = () => openUploader();
return (
@ -55,7 +58,7 @@ function UploadButton({
className="desktop-button"
color={color ?? 'secondary'}
startIcon={icon ?? <FileUploadOutlinedIcon />}>
{text ?? constants.UPLOAD}
{text ?? t('UPLOAD')}
</Button>
<IconButton

View file

@ -1,5 +1,6 @@
import { Dialog, DialogContent } from '@mui/material';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import { UPLOAD_STAGES, UPLOAD_RESULT } from 'constants/upload';
import React, { useContext, useEffect, useState } from 'react';
import { UploadProgressFooter } from './footer';
@ -13,6 +14,8 @@ import { APP_DOWNLOAD_URL } from 'utils/common';
import { ENTE_WEBSITE_LINK } from 'constants/urls';
export function UploadProgressDialog() {
const { t } = useTranslation();
const { open, onClose, uploadStage, finishedUploads } = useContext(
UploadProgressContext
);
@ -54,70 +57,70 @@ export function UploadProgressDialog() {
<>
<ResultSection
uploadResult={UPLOAD_RESULT.UPLOADED}
sectionTitle={constants.SUCCESSFUL_UPLOADS}
sectionTitle={t('SUCCESSFUL_UPLOADS')}
/>
<ResultSection
uploadResult={
UPLOAD_RESULT.UPLOADED_WITH_STATIC_THUMBNAIL
}
sectionTitle={
constants.THUMBNAIL_GENERATION_FAILED_UPLOADS
}
sectionInfo={
constants.THUMBNAIL_GENERATION_FAILED_INFO
}
sectionTitle={t(
'THUMBNAIL_GENERATION_FAILED_UPLOADS'
)}
sectionInfo={t(
'THUMBNAIL_GENERATION_FAILED_INFO'
)}
/>
{uploadStage === UPLOAD_STAGES.FINISH &&
hasUnUploadedFiles && (
<NotUploadSectionHeader>
{constants.FILE_NOT_UPLOADED_LIST}
{t('FILE_NOT_UPLOADED_LIST')}
</NotUploadSectionHeader>
)}
<ResultSection
uploadResult={UPLOAD_RESULT.BLOCKED}
sectionTitle={constants.BLOCKED_UPLOADS}
sectionInfo={constants.ETAGS_BLOCKED(
APP_DOWNLOAD_URL
)}
sectionTitle={t('BLOCKED_UPLOADS')}
sectionInfo={t('ETAGS_BLOCKED', {
url: APP_DOWNLOAD_URL,
})}
/>
<ResultSection
uploadResult={UPLOAD_RESULT.FAILED}
sectionTitle={constants.FAILED_UPLOADS}
sectionTitle={t('FAILED_UPLOADS')}
/>
<ResultSection
uploadResult={UPLOAD_RESULT.SKIPPED_VIDEOS}
sectionTitle={constants.SKIPPED_VIDEOS}
sectionInfo={constants.SKIPPED_VIDEOS_INFO(
ENTE_WEBSITE_LINK
)}
sectionTitle={t('SKIPPED_VIDEOS')}
sectionInfo={t('SKIPPED_VIDEOS_INFO', {
link: ENTE_WEBSITE_LINK,
})}
/>
<ResultSection
uploadResult={UPLOAD_RESULT.ALREADY_UPLOADED}
sectionTitle={constants.SKIPPED_FILES}
sectionInfo={constants.SKIPPED_INFO}
sectionTitle={t('SKIPPED_FILES')}
sectionInfo={t('SKIPPED_INFO')}
/>
<ResultSection
uploadResult={
UPLOAD_RESULT.LARGER_THAN_AVAILABLE_STORAGE
}
sectionTitle={
constants.LARGER_THAN_AVAILABLE_STORAGE_UPLOADS
}
sectionInfo={
constants.LARGER_THAN_AVAILABLE_STORAGE_INFO
}
sectionTitle={t(
'LARGER_THAN_AVAILABLE_STORAGE_UPLOADS'
)}
sectionInfo={t(
'LARGER_THAN_AVAILABLE_STORAGE_INFO'
)}
/>
<ResultSection
uploadResult={UPLOAD_RESULT.UNSUPPORTED}
sectionTitle={constants.UNSUPPORTED_FILES}
sectionInfo={constants.UNSUPPORTED_INFO}
sectionTitle={t('UNSUPPORTED_FILES')}
sectionInfo={t('UNSUPPORTED_INFO')}
/>
<ResultSection
uploadResult={UPLOAD_RESULT.TOO_LARGE}
sectionTitle={constants.TOO_LARGE_UPLOADS}
sectionInfo={constants.TOO_LARGE_INFO}
sectionTitle={t('TOO_LARGE_UPLOADS')}
sectionInfo={t('TOO_LARGE_INFO')}
/>
</>
)}

View file

@ -1,10 +1,13 @@
import { Button, DialogActions } from '@mui/material';
import { UPLOAD_STAGES, UPLOAD_RESULT } from 'constants/upload';
import React, { useContext } from 'react';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import UploadProgressContext from 'contexts/uploadProgress';
export function UploadProgressFooter() {
const { t } = useTranslation();
const { uploadStage, finishedUploads, retryFailed, onClose } = useContext(
UploadProgressContext
);
@ -15,11 +18,11 @@ export function UploadProgressFooter() {
(finishedUploads?.get(UPLOAD_RESULT.FAILED)?.length > 0 ||
finishedUploads?.get(UPLOAD_RESULT.BLOCKED)?.length > 0 ? (
<Button variant="contained" fullWidth onClick={retryFailed}>
{constants.RETRY_FAILED}
{t('RETRY_FAILED')}
</Button>
) : (
<Button variant="contained" fullWidth onClick={onClose}>
{constants.CLOSE}
{t('CLOSE')}
</Button>
))}
</DialogActions>

View file

@ -9,10 +9,13 @@ import {
UploadProgressSectionTitle,
} from './section';
import UploadProgressContext from 'contexts/uploadProgress';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import { UPLOAD_STAGES } from 'constants/upload';
export const InProgressSection = () => {
const { t } = useTranslation();
const { inProgressUploads, hasLivePhotos, uploadFileNames, uploadStage } =
useContext(UploadProgressContext);
const fileList = inProgressUploads ?? [];
@ -21,12 +24,12 @@ export const InProgressSection = () => {
<UploadProgressSection>
<UploadProgressSectionTitle expandIcon={<ExpandMoreIcon />}>
{uploadStage === UPLOAD_STAGES.EXTRACTING_METADATA
? constants.INPROGRESS_METADATA_EXTRACTION
: constants.INPROGRESS_UPLOADS}
? t('INPROGRESS_METADATA_EXTRACTION')
: t('INPROGRESS_UPLOADS')}
</UploadProgressSectionTitle>
<UploadProgressSectionContent>
{hasLivePhotos && (
<SectionInfo>{constants.LIVE_PHOTOS_DETECTED}</SectionInfo>
<SectionInfo>{t('LIVE_PHOTOS_DETECTED')}</SectionInfo>
)}
<FileList
fileList={fileList.map(({ localFileID, progress }) => (

View file

@ -2,7 +2,8 @@ import { UploadProgressDialog } from './dialog';
import { MinimizedUploadProgress } from './minimized';
import React, { useContext, useEffect, useState } from 'react';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import { UPLOAD_STAGES } from 'constants/upload';
import { AppContext } from 'pages/_app';
import {
@ -40,6 +41,8 @@ export default function UploadProgress({
finishedUploads,
...props
}: Props) {
const { t } = useTranslation();
const appContext = useContext(AppContext);
const [expanded, setExpanded] = useState(true);
@ -55,15 +58,15 @@ export default function UploadProgress({
function confirmCancelUpload() {
appContext.setDialogMessage({
title: constants.STOP_UPLOADS_HEADER,
content: constants.STOP_ALL_UPLOADS_MESSAGE,
title: t('STOP_UPLOADS_HEADER'),
content: t('STOP_ALL_UPLOADS_MESSAGE'),
proceed: {
text: constants.YES_STOP_UPLOADS,
text: t('YES_STOP_UPLOADS'),
variant: 'danger',
action: props.cancelUploads,
},
close: {
text: constants.NO,
text: t('NO'),
variant: 'secondary',
action: () => {},
},

View file

@ -3,27 +3,33 @@ import Close from '@mui/icons-material/Close';
import { DialogTitle, Box, Typography, Stack } from '@mui/material';
import { IconButtonWithBG, SpaceBetweenFlex } from 'components/Container';
import { UPLOAD_STAGES } from 'constants/upload';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import UploadProgressContext from 'contexts/uploadProgress';
import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
const UploadProgressTitleText = ({ expanded }) => (
<Typography variant={expanded ? 'title' : 'subtitle'}>
{constants.FILE_UPLOAD}
</Typography>
);
const UploadProgressTitleText = ({ expanded }) => {
const { t } = useTranslation();
return (
<Typography variant={expanded ? 'title' : 'subtitle'}>
{t('FILE_UPLOAD')}
</Typography>
);
};
function UploadProgressSubtitleText() {
const { t } = useTranslation();
const { uploadStage, uploadCounter } = useContext(UploadProgressContext);
return (
<Typography color="text.secondary">
{uploadStage === UPLOAD_STAGES.UPLOADING
? constants.UPLOAD_STAGE_MESSAGE[uploadStage](uploadCounter)
? t('UPLOAD_STAGE_MESSAGE[uploadStage]', { uploadCounter })
: uploadStage === UPLOAD_STAGES.EXTRACTING_METADATA
? constants.UPLOAD_STAGE_MESSAGE[uploadStage](uploadCounter)
: constants.UPLOAD_STAGE_MESSAGE[uploadStage]}
? t('UPLOAD_STAGE_MESSAGE[uploadStage]', { uploadCounter })
: t('UPLOAD_STAGE_MESSAGE[uploadStage]')}
</Typography>
);
}

View file

@ -4,7 +4,7 @@ import DialogTitleWithCloseButton, {
dialogCloseHandler,
} from 'components/DialogBox/TitleWithCloseButton';
import React from 'react';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
interface Props {
uploadToMultipleCollection: () => void;
@ -17,6 +17,7 @@ function UploadStrategyChoiceModal({
uploadToSingleCollection,
...props
}: Props) {
const { t } = useTranslation();
const handleClose = dialogCloseHandler({
onClose: props.onClose,
});
@ -24,12 +25,12 @@ function UploadStrategyChoiceModal({
return (
<Dialog open={props.open} onClose={handleClose}>
<DialogTitleWithCloseButton onClose={handleClose}>
{constants.MULTI_FOLDER_UPLOAD}
{t('MULTI_FOLDER_UPLOAD')}
</DialogTitleWithCloseButton>
<DialogContent>
<CenteredFlex mb={1}>
<Typography color="text.secondary">
{constants.UPLOAD_STRATEGY_CHOICE}
{t('UPLOAD_STRATEGY_CHOICE')}
</Typography>
</CenteredFlex>
<SpaceBetweenFlex px={2}>
@ -40,10 +41,10 @@ function UploadStrategyChoiceModal({
props.onClose();
uploadToSingleCollection();
}}>
{constants.UPLOAD_STRATEGY_SINGLE_COLLECTION}
{t('UPLOAD_STRATEGY_SINGLE_COLLECTION')}
</Button>
<strong>{constants.OR}</strong>
<strong>{t('OR')}</strong>
<Button
size="medium"
@ -52,7 +53,7 @@ function UploadStrategyChoiceModal({
props.onClose();
uploadToMultipleCollection();
}}>
{constants.UPLOAD_STRATEGY_COLLECTION_PER_FOLDER}
{t('UPLOAD_STRATEGY_COLLECTION_PER_FOLDER')}
</Button>
</SpaceBetweenFlex>
</DialogContent>

View file

@ -1,5 +1,6 @@
import React, { useContext, useEffect, useRef } from 'react';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import { default as FileUploadIcon } from '@mui/icons-material/ImageOutlined';
import { default as FolderUploadIcon } from '@mui/icons-material/PermMediaOutlined';
import GoogleIcon from '@mui/icons-material/Google';
@ -27,6 +28,8 @@ export default function UploadTypeSelector({
uploadGoogleTakeoutZips,
uploadTypeSelectorIntent,
}: Iprops) {
const { t } = useTranslation();
const publicCollectionGalleryContext = useContext(
PublicCollectionGalleryContext
);
@ -57,11 +60,11 @@ export default function UploadTypeSelector({
<DialogTitleWithCloseButton onClose={onClose}>
{uploadTypeSelectorIntent ===
UploadTypeSelectorIntent.collectPhotos
? constants.SELECT_PHOTOS
? t('SELECT_PHOTOS')
: uploadTypeSelectorIntent ===
UploadTypeSelectorIntent.import
? constants.IMPORT
: constants.UPLOAD}
? t('IMPORT')
: t('UPLOAD')}
</DialogTitleWithCloseButton>
<Box p={1.5} pt={0.5}>
<Stack spacing={0.5}>
@ -70,25 +73,25 @@ export default function UploadTypeSelector({
<UploadTypeOption
onClick={uploadFiles}
startIcon={<FileUploadIcon />}>
{constants.UPLOAD_FILES}
{t('UPLOAD_FILES')}
</UploadTypeOption>
)}
<UploadTypeOption
onClick={uploadFolders}
startIcon={<FolderUploadIcon />}>
{constants.UPLOAD_DIRS}
{t('UPLOAD_DIRS')}
</UploadTypeOption>
{uploadTypeSelectorIntent !==
UploadTypeSelectorIntent.collectPhotos && (
<UploadTypeOption
onClick={uploadGoogleTakeoutZips}
startIcon={<GoogleIcon />}>
{constants.UPLOAD_GOOGLE_TAKEOUT}
{t('UPLOAD_GOOGLE_TAKEOUT')}
</UploadTypeOption>
)}
</Stack>
<Typography p={1.5} pt={4} color="text.secondary">
{constants.DRAG_AND_DROP_HINT}
{t('DRAG_AND_DROP_HINT')}
</Typography>
</Box>
</Dialog>

View file

@ -1,7 +1,8 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
import { syncCollections, createAlbum } from 'services/collectionService';
import constants from 'utils/strings/constants';
import { Trans, useTranslation } from 'react-i18next';
import UploadProgress from './UploadProgress';
import UploadStrategyChoiceModal from './UploadStrategyChoiceModal';
@ -86,6 +87,8 @@ interface Props {
}
export default function Uploader(props: Props) {
const { t } = useTranslation();
const appContext = useContext(AppContext);
const galleryContext = useContext(GalleryContext);
const publicCollectionGalleryContext = useContext(
@ -256,12 +259,24 @@ export default function Uploader(props: Props) {
if (isCanvasBlocked()) {
addLogLine('canvas blocked, blocking upload');
appContext.setDialogMessage({
title: constants.CANVAS_BLOCKED_TITLE,
title: t('CANVAS_BLOCKED_TITLE'),
content: constants.CANVAS_BLOCKED_MESSAGE(),
close: { text: constants.CLOSE },
content: (
<Trans i18nKey="CANVAS_BLOCKED_MESSAGE">
<p>
It looks like your browser has disabled access
to canvas, which is necessary to generate
thumbnails for your photos
</p>
<p>
Please enable access to your browser's canvas,
or check out our desktop app
</p>
</Trans>
),
close: { text: t('CLOSE') },
proceed: {
text: constants.DOWNLOAD,
text: t('DOWNLOAD'),
action: downloadApp,
variant: 'accent',
},
@ -411,10 +426,10 @@ export default function Uploader(props: Props) {
closeUploadProgress();
logError(e, 'Failed to create album');
appContext.setDialogMessage({
title: constants.ERROR,
title: t('ERROR'),
close: { variant: 'danger' },
content: constants.CREATE_ALBUM_FAILED,
content: t('CREATE_ALBUM_FAILED'),
});
throw e;
}
@ -541,16 +556,16 @@ export default function Uploader(props: Props) {
case CustomError.SUBSCRIPTION_EXPIRED:
notification = {
variant: 'danger',
subtext: constants.SUBSCRIPTION_EXPIRED,
message: constants.RENEW_NOW,
subtext: t('SUBSCRIPTION_EXPIRED'),
message: t('RENEW_NOW'),
onClick: () => billingService.redirectToCustomerPortal(),
};
break;
case CustomError.STORAGE_QUOTA_EXCEEDED:
notification = {
variant: 'danger',
subtext: constants.STORAGE_QUOTA_EXCEEDED,
message: constants.UPGRADE_NOW,
subtext: t('STORAGE_QUOTA_EXCEEDED'),
message: t('UPGRADE_NOW'),
onClick: () => galleryContext.showPlanSelectorModal(),
startIcon: <DiscFullIcon />,
};
@ -558,7 +573,7 @@ export default function Uploader(props: Props) {
default:
notification = {
variant: 'danger',
message: constants.UNKNOWN_ERROR,
message: t('UNKNOWN_ERROR'),
onClick: () => null,
};
}
@ -578,8 +593,8 @@ export default function Uploader(props: Props) {
};
const showCollectionCreateModal = () => {
props.setCollectionNamerAttributes({
title: constants.CREATE_COLLECTION,
buttonText: constants.CREATE,
title: t('CREATE_COLLECTION'),
buttonText: t('CREATE'),
autoFilledName: null,
callback: uploadToSingleNewCollection,
});
@ -647,7 +662,7 @@ export default function Uploader(props: Props) {
callback: uploadFilesToExistingCollection,
onCancel: handleCollectionSelectorCancel,
showNextModal,
title: constants.UPLOAD_TO_COLLECTION,
title: t('UPLOAD_TO_COLLECTION'),
});
} catch (e) {
logError(e, 'handleCollectionCreationAndUpload failed');

View file

@ -1,9 +1,9 @@
import React from 'react';
import constants from 'utils/strings/constants';
import DialogBox from './DialogBox';
import AutoAwesomeOutlinedIcon from '@mui/icons-material/AutoAwesomeOutlined';
import { Typography } from '@mui/material';
import SingleInputForm from './SingleInputForm';
import { useTranslation } from 'react-i18next';
export default function UserNameInputDialog({
open,
@ -12,6 +12,7 @@ export default function UserNameInputDialog({
toUploadFilesCount,
uploaderName,
}) {
const { t } = useTranslation();
const handleSubmit = async (inputValue: string) => {
onClose();
await onNameSubmit(inputValue);
@ -22,18 +23,18 @@ export default function UserNameInputDialog({
open={open}
onClose={onClose}
attributes={{
title: constants.ENTER_NAME,
title: t('ENTER_NAME'),
icon: <AutoAwesomeOutlinedIcon />,
}}>
<Typography color={'text.secondary'} pb={1}>
{constants.PUBLIC_UPLOADER_NAME_MESSAGE}
{t('PUBLIC_UPLOADER_NAME_MESSAGE')}
</Typography>
<SingleInputForm
hiddenLabel
initialValue={uploaderName}
callback={handleSubmit}
placeholder={constants.NAME_PLACEHOLDER}
buttonText={constants.ADD_X_PHOTOS(toUploadFilesCount)}
placeholder={t('NAME_PLACEHOLDER')}
buttonText={t('add_photos', { count: toUploadFilesCount })}
fieldType="text"
blockButton
secondaryButtonAction={onClose}

View file

@ -1,6 +1,5 @@
import React from 'react';
import constants from 'utils/strings/constants';
import SingleInputForm, {
SingleInputFormProps,
} from 'components/SingleInputForm';
@ -10,6 +9,7 @@ import { CustomError } from 'utils/error';
import { Input } from '@mui/material';
import { KeyAttributes, User } from 'types/user';
import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker';
import { useTranslation } from 'react-i18next';
export interface VerifyMasterPasswordFormProps {
user: User;
@ -24,6 +24,7 @@ export default function VerifyMasterPasswordForm({
callback,
buttonText,
}: VerifyMasterPasswordFormProps) {
const { t } = useTranslation();
const verifyPassphrase: SingleInputFormProps['callback'] = async (
passphrase,
setFieldError
@ -56,13 +57,13 @@ export default function VerifyMasterPasswordForm({
} catch (e) {
switch (e.message) {
case CustomError.WEAK_DEVICE:
setFieldError(constants.WEAK_DEVICE);
setFieldError(t('WEAK_DEVICE'));
break;
case CustomError.INCORRECT_PASSWORD:
setFieldError(constants.INCORRECT_PASSPHRASE);
setFieldError(t('INCORRECT_PASSPHRASE'));
break;
default:
setFieldError(`${constants.UNKNOWN_ERROR} ${e.message}`);
setFieldError(`${t('UNKNOWN_ERROR')} ${e.message}`);
}
}
};
@ -70,7 +71,7 @@ export default function VerifyMasterPasswordForm({
return (
<SingleInputForm
callback={verifyPassphrase}
placeholder={constants.RETURN_PASSPHRASE_HINT}
placeholder={t('RETURN_PASSPHRASE_HINT')}
buttonText={buttonText}
hiddenPreInput={
<Input

View file

@ -4,7 +4,8 @@ import { Button, Dialog, DialogContent, Stack } from '@mui/material';
import watchFolderService from 'services/watchFolder/watchFolderService';
import { WatchMapping } from 'types/watchFolder';
import { AppContext } from 'pages/_app';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import DialogTitleWithCloseButton from 'components/DialogBox/TitleWithCloseButton';
import UploadStrategyChoiceModal from 'components/Upload/UploadStrategyChoiceModal';
import { UPLOAD_STRATEGY } from 'constants/upload';
@ -18,6 +19,8 @@ interface Iprops {
}
export default function WatchFolder({ open, onClose }: Iprops) {
const { t } = useTranslation();
const [mappings, setMappings] = useState<WatchMapping[]>([]);
const [inputFolderPath, setInputFolderPath] = useState('');
const [choiceModalOpen, setChoiceModalOpen] = useState(false);
@ -112,7 +115,7 @@ export default function WatchFolder({ open, onClose }: Iprops) {
<DialogTitleWithCloseButton
onClose={onClose}
sx={{ '&&&': { padding: '32px 16px 16px 24px' } }}>
{constants.WATCHED_FOLDERS}
{t('WATCHED_FOLDERS')}
</DialogTitleWithCloseButton>
<DialogContent sx={{ flex: 1 }}>
<Stack spacing={1} p={1.5} height={'100%'}>
@ -129,7 +132,7 @@ export default function WatchFolder({ open, onClose }: Iprops) {
style={{
marginLeft: '8px',
}}></span>
{constants.ADD_FOLDER}
{t('ADD_FOLDER')}
</Button>
</Stack>
</DialogContent>

View file

@ -6,7 +6,8 @@ import { WatchMapping } from 'types/watchFolder';
import { AppContext } from 'pages/_app';
import FolderOpenIcon from '@mui/icons-material/FolderOpen';
import FolderCopyOutlinedIcon from '@mui/icons-material/FolderCopyOutlined';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import MappingEntryOptions from './mappingEntryOptions';
import { EntryHeading } from './entryHeading';
import { UPLOAD_STRATEGY } from 'constants/upload';
@ -17,6 +18,8 @@ interface Iprops {
}
export function MappingEntry({ mapping, handleRemoveMapping }: Iprops) {
const { t } = useTranslation();
const appContext = React.useContext(AppContext);
const stopWatching = () => {
@ -25,15 +28,15 @@ export function MappingEntry({ mapping, handleRemoveMapping }: Iprops) {
const confirmStopWatching = () => {
appContext.setDialogMessage({
title: constants.STOP_WATCHING_FOLDER,
content: constants.STOP_WATCHING_DIALOG_MESSAGE,
title: t('STOP_WATCHING_FOLDER'),
content: t('STOP_WATCHING_DIALOG_MESSAGE'),
close: {
text: constants.CANCEL,
text: t('CANCEL'),
variant: 'secondary',
},
proceed: {
action: stopWatching,
text: constants.YES_STOP,
text: t('YES_STOP'),
variant: 'danger',
},
});
@ -44,11 +47,11 @@ export function MappingEntry({ mapping, handleRemoveMapping }: Iprops) {
<HorizontalFlex>
{mapping &&
mapping.uploadStrategy === UPLOAD_STRATEGY.SINGLE_COLLECTION ? (
<Tooltip title={constants.UPLOADED_TO_SINGLE_COLLECTION}>
<Tooltip title={t('UPLOADED_TO_SINGLE_COLLECTION')}>
<FolderOpenIcon />
</Tooltip>
) : (
<Tooltip title={constants.UPLOADED_TO_SEPARATE_COLLECTIONS}>
<Tooltip title={t('UPLOADED_TO_SEPARATE_COLLECTIONS')}>
<FolderCopyOutlinedIcon />
</Tooltip>
)}

View file

@ -1,5 +1,6 @@
import React from 'react';
import constants from 'utils/strings/constants';
import { useTranslation } from 'react-i18next';
import DoNotDisturbOutlinedIcon from '@mui/icons-material/DoNotDisturbOutlined';
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
import OverflowMenu from 'components/OverflowMenu/menu';
@ -10,6 +11,8 @@ interface Iprops {
}
export default function MappingEntryOptions({ confirmStopWatching }: Iprops) {
const { t } = useTranslation();
return (
<OverflowMenu
menuPaperProps={{
@ -24,7 +27,7 @@ export default function MappingEntryOptions({ confirmStopWatching }: Iprops) {
color="danger"
onClick={confirmStopWatching}
startIcon={<DoNotDisturbOutlinedIcon />}>
{constants.STOP_WATCHING}
{t('STOP_WATCHING')}
</OverflowMenuOption>
</OverflowMenu>
);

Some files were not shown because too many files have changed in this diff Show more