Merge remote-tracking branch 'origin/main' into auth-linux-fixes

This commit is contained in:
Prateek Sunal
2024-09-28 21:42:51 +05:30
75 changed files with 2097 additions and 1065 deletions

View File

@@ -16,8 +16,8 @@ SetupIconFile={{SETUP_ICON_FILE}}
WizardStyle=modern
;PrivilegesRequired={{PRIVILEGES_REQUIRED}}
PrivilegesRequiredOverridesAllowed=dialog
ArchitecturesAllowed=x64
ArchitecturesInstallIn64BitMode=x64
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
UninstallDisplayIcon={app}\auth.exe
[Languages]

View File

@@ -1,11 +1,16 @@
# CHANGELOG
## v1.7.5 (Unreleased)
## v1.7.6 (Unreleased)
- Directly upload to selected album on drag and drop.
- Include shared files in export.
- .
## v1.7.5
- Face grouping (beta).
- Include shared files in export.
- Directly upload to selected album on drag and drop.
- Improve heuristics for clubbing a photo and video into a live photo.
## v1.7.4
- Improved date search, including support for day of week and hour of day.

View File

@@ -1,6 +1,6 @@
{
"name": "ente",
"version": "1.7.5-beta",
"version": "1.7.6-beta",
"private": true,
"description": "Desktop client for Ente Photos",
"repository": "github:ente-io/photos-desktop",

View File

@@ -7,45 +7,41 @@ description:
# Machine learning
> [!NOTE]
>
> This document describes a beta feature that will be present in an upcoming
> release.
Ente supports on-device machine learning. This allows you to use the latest
advances in AI in a privacy preserving manner.
- You can search for your photos by the **faces** of the people in them. Ente
- You can search for your photos by the **Faces** of the people in them. Ente
will show you all the faces in a photo, and will also try to group similar
faces together to create clusters of people so that you can give them names,
and quickly find all photos with a given person in them.
- You can search for your photos by typing natural language descriptions of
them. For example, you can search for "night", "by the seaside", or "the red
motorcycle next to a fountain". Within the app, this ability is sometimes
referred to as **magic search**.
motorcycle next to a fountain". Within the app, this ability is referred to
as **Magic search**.
- We will build on this foundation to add more forms of advanced search.
You can enable face recognition and magic search in the app's preferences on
either the mobile app or the desktop app.
You can enable face and magic search in the app's preferences on either the
mobile app or the desktop app.
On mobile, this is available under `General > Advanced > Machine learning`.
If you have a big library, we recommend enabling this on the desktop app first,
because it can index your existing photos faster (The app needs to download your
originals to index them which can happen faster over WiFi, and indexing is also
faster on your computer as compared to your mobile device).
On desktop, this is available under `Preferences > Machine learning`.
Once your existing photos have been indexed, then you can use either. The mobile
app is fast enough to easily and seamlessly index the new photos that you take.
---
The app needs to download your original photos to index them. This is faster
over WiFi. Indexing is also faster on your computer as compared to your mobile
device.
> [!TIP]
>
> Even for the initial indexing, you don't necessarily need the desktop app, it
> just will be a bit faster.
> If you have a large library on Ente, we recommend enabling this feature on the
> desktop app first, because it can index your existing photos faster. Once your
> existing photos have been indexed, then you can use either. The mobile app is
> fast enough to index new photos as they are being backed up.
The indexes are synced across all your devices automatically using the same
end-to-end encypted security that we use for syncing your photos.
end-to-end encrypted security that we use for syncing your photos.
Note that the desktop app does not currently support viewing and modifying the
automatically generated face groupings, that is only supported by the mobile
app.
Note that the desktop app does not currently support modifying the face
groupings, that is only supported by the mobile app.

View File

@@ -133,7 +133,7 @@ class MLService {
}
if (_mlControllerStatus == true) {
// refresh discover section
MagicCacheService.instance.updateCache().ignore();
MagicCacheService.instance.updateCache(forced: force).ignore();
}
await indexAllImages();
if ((await MLDataDB.instance.getUnclusteredFaceCount()) > 0) {

View File

@@ -205,7 +205,7 @@ class MagicCacheService {
queueUpdate("Prompts data updated");
} else if (lastMagicCacheUpdateTime <
DateTime.now()
.subtract(const Duration(days: 1))
.subtract(const Duration(hours: 12))
.millisecondsSinceEpoch) {
queueUpdate("Cache is old");
}

View File

@@ -342,7 +342,8 @@ Future<MediaUploadData> _getMediaUploadDataFromAppCache(EnteFile file) async {
Map<String, int>? dimensions;
if (file.fileType == FileType.image) {
dimensions = await getImageHeightAndWith(imagePath: localPath);
} else {
} else if (thumbnailData != null) {
// the thumbnail null check is to ensure that we are able to generate thum
// for video, we need to use the thumbnail data with any max width/height
final thumbnailFilePath = await VideoThumbnail.thumbnailFile(
video: localPath,
@@ -406,14 +407,19 @@ Future<Uint8List?> getThumbnailFromInAppCacheFile(EnteFile file) async {
return null;
}
if (file.fileType == FileType.video) {
final thumbnailFilePath = await VideoThumbnail.thumbnailFile(
video: localFile.path,
imageFormat: ImageFormat.JPEG,
thumbnailPath: (await getTemporaryDirectory()).path,
maxWidth: thumbnailLargeSize,
quality: 80,
);
localFile = File(thumbnailFilePath!);
try {
final thumbnailFilePath = await VideoThumbnail.thumbnailFile(
video: localFile.path,
imageFormat: ImageFormat.JPEG,
thumbnailPath: (await getTemporaryDirectory()).path,
maxWidth: thumbnailLargeSize,
quality: 80,
);
localFile = File(thumbnailFilePath!);
} catch (e) {
_logger.warning('Failed to generate video thumbnail', e);
return null;
}
}
var thumbnailData = await localFile.readAsBytes();
int compressionAttempts = 0;

View File

@@ -158,7 +158,7 @@ const AuthNavbar: React.FC = () => {
startIcon={<LogoutOutlined />}
onClick={logout}
>
{t("LOGOUT")}
{t("logout")}
</OverflowMenuOption>
</OverflowMenu>
</HorizontalFlex>

View File

@@ -9,7 +9,6 @@ import {
type CollectionsSortBy,
type CollectionSummaries,
} from "@/new/photos/types/collection";
import { ensure } from "@/utils/ensure";
import { includes } from "@/utils/type-guards";
import {
getData,
@@ -87,6 +86,7 @@ type CollectionsProps = Omit<
*/
export const GalleryBarAndListHeader: React.FC<CollectionsProps> = ({
shouldHide,
showPeopleSectionButton,
mode,
onChangeMode,
collectionSummaries,
@@ -95,7 +95,7 @@ export const GalleryBarAndListHeader: React.FC<CollectionsProps> = ({
setActiveCollectionID,
hiddenCollectionSummaries,
people,
activePersonID,
activePerson,
onSelectPerson,
setCollectionNamerAttributes,
setPhotoListHeader,
@@ -171,14 +171,13 @@ export const GalleryBarAndListHeader: React.FC<CollectionsProps> = ({
}
onCollectionCast={() => setOpenAlbumCastDialog(true)}
/>
) : (
) : activePerson ? (
<PeopleHeader
person={ensure(
people.find((p) => p.id == activePersonID) ??
people[0],
)}
person={activePerson}
{...{ onSelectPerson, appContext }}
/>
) : (
<></>
),
itemType: ITEM_TYPE.HEADER,
height: 68,
@@ -189,8 +188,7 @@ export const GalleryBarAndListHeader: React.FC<CollectionsProps> = ({
toShowCollectionSummaries,
activeCollectionID,
isActiveCollectionDownloadInProgress,
people,
activePersonID,
activePerson,
]);
if (shouldBeHidden) {
@@ -201,11 +199,12 @@ export const GalleryBarAndListHeader: React.FC<CollectionsProps> = ({
<>
<GalleryBarImpl
{...{
showPeopleSectionButton,
mode,
onChangeMode,
activeCollectionID,
people,
activePersonID,
activePerson,
onSelectPerson,
collectionsSortBy,
}}

View File

@@ -66,7 +66,7 @@ export default function ExportFinished(props: Props) {
</DialogContent>
<DialogActions>
<Button color="secondary" size="large" onClick={props.onHide}>
{t("CLOSE")}
{t("close")}
</Button>
<Button size="large" color="primary" onClick={props.onResync}>
{t("EXPORT_AGAIN")}

View File

@@ -91,7 +91,7 @@ export default function ExportInProgress(props: Props) {
size="large"
onClick={props.closeExportDialog}
>
{t("CLOSE")}
{t("close")}
</Button>
<Button
size="large"

View File

@@ -65,7 +65,7 @@ const ExportPendingList = (props: Iprops) => {
title: t("PENDING_ITEMS"),
close: {
action: props.onClose,
text: t("CLOSE"),
text: t("close"),
},
}}
>

View File

@@ -82,7 +82,7 @@ export const FilesDownloadProgress: React.FC<FilesDownloadProgressProps> = ({
},
},
close: {
text: t("NO"),
text: t("no"),
variant: "secondary",
action: () => {},
},

View File

@@ -252,7 +252,7 @@ const Footer = ({ step, startFix, ...props }) => {
)}
{step == "completed" && (
<Button color="primary" size="large" onClick={props.hide}>
{t("CLOSE")}
{t("close")}
</Button>
)}
{(!step || step == "completed-with-errors") && (

View File

@@ -8,7 +8,7 @@ import { PHOTOS_PAGES } from "@ente/shared/constants/pages";
import { CustomError } from "@ente/shared/error";
import useMemoSingleThreaded from "@ente/shared/hooks/useMemoSingleThreaded";
import { styled } from "@mui/material";
import PhotoViewer from "components/PhotoViewer";
import PhotoViewer, { type PhotoViewerProps } from "components/PhotoViewer";
import { useRouter } from "next/router";
import { GalleryContext } from "pages/gallery";
import PhotoSwipe from "photoswipe";
@@ -72,6 +72,7 @@ interface Props {
isInHiddenSection?: boolean;
setFilesDownloadProgressAttributesCreator?: SetFilesDownloadProgressAttributesCreator;
selectable?: boolean;
onSelectPerson?: PhotoViewerProps["onSelectPerson"];
}
const PhotoFrame = ({
@@ -95,6 +96,7 @@ const PhotoFrame = ({
isInHiddenSection,
setFilesDownloadProgressAttributesCreator,
selectable,
onSelectPerson,
}: Props) => {
const [open, setOpen] = useState(false);
const [currentIndex, setCurrentIndex] = useState<number>(0);
@@ -580,6 +582,7 @@ const PhotoFrame = ({
setFilesDownloadProgressAttributesCreator={
setFilesDownloadProgressAttributesCreator
}
onSelectPerson={onSelectPerson}
/>
</Container>
);

View File

@@ -510,7 +510,7 @@ export function PhotoList({
itemType: ITEM_TYPE.OTHER,
item: (
<NothingContainer span={columns}>
<div>{t("NOTHING_HERE")}</div>
<div>{t("nothing_here")}</div>
</NothingContainer>
),
id: "empty-list-banner",

View File

@@ -12,11 +12,19 @@ import {
type ParsedMetadataDate,
} from "@/media/file-metadata";
import { FileType } from "@/media/file-type";
import { UnidentifiedFaces } from "@/new/photos/components/PeopleList";
import {
AnnotatedFacePeopleList,
UnclusteredFaceList,
} from "@/new/photos/components/PeopleList";
import { PhotoDateTimePicker } from "@/new/photos/components/PhotoDateTimePicker";
import { photoSwipeZIndex } from "@/new/photos/components/PhotoViewer";
import { tagNumericValue, type RawExifTags } from "@/new/photos/services/exif";
import { isMLEnabled } from "@/new/photos/services/ml";
import {
AnnotatedFacesForFile,
getAnnotatedFacesForFile,
isMLEnabled,
type AnnotatedFaceID,
} from "@/new/photos/services/ml";
import { EnteFile } from "@/new/photos/types/file";
import { formattedByteSize } from "@/new/photos/utils/units";
import CopyButton from "@ente/shared/components/CodeBlock/CopyButton";
@@ -61,7 +69,7 @@ export interface FileInfoExif {
parsed: ParsedMetadata | undefined;
}
interface FileInfoProps {
export interface FileInfoProps {
showInfo: boolean;
handleCloseInfo: () => void;
closePhotoViewer: () => void;
@@ -73,6 +81,10 @@ interface FileInfoProps {
fileToCollectionsMap?: Map<number, number[]>;
collectionNameMap?: Map<number, string>;
showCollectionChips: boolean;
/**
* Called when the user selects a person in the file info panel.
*/
onSelectPerson?: ((personID: string) => void) | undefined;
}
export const FileInfo: React.FC<FileInfoProps> = ({
@@ -87,6 +99,7 @@ export const FileInfo: React.FC<FileInfoProps> = ({
collectionNameMap,
showCollectionChips,
closePhotoViewer,
onSelectPerson,
}) => {
const { mapEnabled, updateMapEnabled, setDialogBoxAttributesV2 } =
useContext(AppContext);
@@ -97,6 +110,9 @@ export const FileInfo: React.FC<FileInfoProps> = ({
const [exifInfo, setExifInfo] = useState<ExifInfo | undefined>();
const [openRawExif, setOpenRawExif] = useState(false);
const [annotatedFaces, setAnnotatedFaces] = useState<
AnnotatedFacesForFile | undefined
>();
const location = useMemo(() => {
if (file) {
@@ -106,6 +122,21 @@ export const FileInfo: React.FC<FileInfoProps> = ({
return exif?.parsed?.location;
}, [file, exif]);
useEffect(() => {
if (!file) return;
let didCancel = false;
void (async () => {
const result = await getAnnotatedFacesForFile(file);
!didCancel && setAnnotatedFaces(result);
})();
return () => {
didCancel = true;
};
}, [file]);
useEffect(() => {
setExifInfo(parseExifInfo(exif));
}, [exif]);
@@ -129,6 +160,13 @@ export const FileInfo: React.FC<FileInfoProps> = ({
getMapDisableConfirmationDialog(() => updateMapEnabled(false)),
);
const handleSelectFace = (annotatedFaceID: AnnotatedFaceID) => {
if (onSelectPerson) {
onSelectPerson(annotatedFaceID.personID);
closePhotoViewer();
}
};
return (
<FileInfoSidebar open={showInfo} onClose={handleCloseInfo}>
<Titlebar onClose={handleCloseInfo} title={t("INFO")} backIsClose />
@@ -267,10 +305,17 @@ export const FileInfo: React.FC<FileInfoProps> = ({
</InfoItem>
)}
{isMLEnabled() && (
{isMLEnabled() && annotatedFaces && (
<>
{/* TODO-Cluster <PhotoPeopleList file={file} /> */}
<UnidentifiedFaces enteFile={file} />
<AnnotatedFacePeopleList
enteFile={file}
annotatedFaceIDs={annotatedFaces.annotatedFaceIDs}
onSelectFace={handleSelectFace}
/>
<UnclusteredFaceList
enteFile={file}
faceIDs={annotatedFaces.otherFaceIDs}
/>
</>
)}
</Stack>

View File

@@ -658,7 +658,7 @@ const ImageEditorOverlay = (props: IProps) => {
/>
</Tabs>
</HorizontalFlex>
<MenuSectionTitle title={t("RESET")} />
<MenuSectionTitle title={t("reset")} />
<MenuItemGroup
style={{
marginBottom: "0.5rem",

View File

@@ -48,7 +48,7 @@ import { SetFilesDownloadProgressAttributesCreator } from "types/gallery";
import { pauseVideo, playVideo } from "utils/photoFrame";
import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
import { getTrashFileMessage } from "utils/ui";
import { FileInfo, type FileInfoExif } from "./FileInfo";
import { FileInfo, type FileInfoExif, type FileInfoProps } from "./FileInfo";
import ImageEditorOverlay from "./ImageEditorOverlay";
import CircularProgressWithLabel from "./styledComponents/CircularProgressWithLabel";
import { ConversionFailedNotification } from "./styledComponents/ConversionFailedNotification";
@@ -98,7 +98,8 @@ const CaptionContainer = styled("div")(({ theme }) => ({
backgroundColor: theme.colors.backdrop.faint,
backdropFilter: `blur(${theme.colors.blur.base})`,
}));
interface Iprops {
export interface PhotoViewerProps {
isOpen: boolean;
items: any[];
currentIndex?: number;
@@ -115,9 +116,10 @@ interface Iprops {
fileToCollectionsMap: Map<number, number[]>;
collectionNameMap: Map<number, string>;
setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator;
onSelectPerson?: FileInfoProps["onSelectPerson"];
}
function PhotoViewer(props: Iprops) {
function PhotoViewer(props: PhotoViewerProps) {
const galleryContext = useContext(GalleryContext);
const appContext = useContext(AppContext);
const publicCollectionGalleryContext = useContext(
@@ -812,7 +814,7 @@ function PhotoViewer(props: Iprops) {
<button
className="pswp__button pswp__button--custom"
title={t("CLOSE_OPTION")}
title={t("close_key")}
onClick={handleClose}
>
<CloseIcon />
@@ -969,6 +971,7 @@ function PhotoViewer(props: Iprops) {
refreshPhotoswipe={refreshPhotoswipe}
fileToCollectionsMap={props.fileToCollectionsMap}
collectionNameMap={props.collectionNameMap}
onSelectPerson={props.onSelectPerson}
/>
<ImageEditorOverlay
show={showImageEditorOverlay}

View File

@@ -506,7 +506,7 @@ const UtilitySection: React.FC<UtilitySectionProps> = ({ closeSidebar }) => {
<EnteMenuItem
variant="secondary"
onClick={openRecoveryKeyModal}
label={t("RECOVERY_KEY")}
label={t("recovery_key")}
/>
{isInternalUserViaEmailCheck() && (
<EnteMenuItem
@@ -600,13 +600,13 @@ const HelpSection: React.FC = () => {
<>
<EnteMenuItem
onClick={requestFeature}
label={t("REQUEST_FEATURE")}
label={t("request_feature")}
variant="secondary"
/>
<EnteMenuItem
onClick={contactSupport}
labelComponent={
<span title="support@ente.io">{t("SUPPORT")}</span>
<span title="support@ente.io">{t("support")}</span>
}
variant="secondary"
/>
@@ -634,9 +634,9 @@ const ExitSection: React.FC = () => {
const confirmLogout = () => {
setDialogMessage({
title: t("LOGOUT_MESSAGE"),
title: t("logout_message"),
proceed: {
text: t("LOGOUT"),
text: t("logout"),
action: logout,
variant: "critical",
},
@@ -649,7 +649,7 @@ const ExitSection: React.FC = () => {
<EnteMenuItem
onClick={confirmLogout}
color="critical"
label={t("LOGOUT")}
label={t("logout")}
variant="secondary"
/>
<EnteMenuItem

View File

@@ -22,7 +22,7 @@ export function UploadProgressFooter() {
</Button>
) : (
<Button variant="contained" fullWidth onClick={onClose}>
{t("CLOSE")}
{t("close")}
</Button>
))}
</DialogActions>

View File

@@ -57,7 +57,7 @@ export default function UploadProgress({
action: props.cancelUploads,
},
close: {
text: t("NO"),
text: t("no"),
variant: "secondary",
action: () => {},
},

View File

@@ -21,7 +21,7 @@ export default function UserNameInputDialog({
<DialogBox maxWidth="xs" open={open} onClose={onClose}>
<DialogIcon icon={<AutoAwesomeOutlinedIcon />} />
<DialogTitle>{t("ENTER_NAME")}</DialogTitle>
<DialogTitle>{t("enter_name")}</DialogTitle>
<DialogContent>
<Typography color={"text.muted"} pb={1}>

View File

@@ -162,7 +162,7 @@ function PlanSelectorCard(props: PlanSelectorCardProps) {
appContext.setDialogMessage({
title: t("OPEN_PLAN_SELECTOR_MODAL_FAILED"),
content: t("UNKNOWN_ERROR"),
close: { text: t("CLOSE"), variant: "secondary" },
close: { text: t("close"), variant: "secondary" },
proceed: {
text: t("REOPEN_PLAN_SELECTOR_MODAL"),
variant: "accent",

View File

@@ -191,7 +191,7 @@ const SelectedFileOptions = ({
<DownloadIcon />
</IconButton>
</Tooltip>
<Tooltip title={t("ADD")}>
<Tooltip title={t("add")}>
<IconButton onClick={addToCollection}>
<AddIcon />
</IconButton>
@@ -225,7 +225,7 @@ const SelectedFileOptions = ({
<DownloadIcon />
</IconButton>
</Tooltip>
<Tooltip title={t("ADD")}>
<Tooltip title={t("add")}>
<IconButton onClick={addToCollection}>
<AddIcon />
</IconButton>
@@ -328,7 +328,7 @@ const SelectedFileOptions = ({
<DownloadIcon />
</IconButton>
</Tooltip>
<Tooltip title={t("ADD")}>
<Tooltip title={t("add")}>
<IconButton onClick={addToCollection}>
<AddIcon />
</IconButton>

View File

@@ -3,8 +3,12 @@ import { NavbarBase } from "@/base/components/Navbar";
import { useIsMobileWidth } from "@/base/hooks";
import log from "@/base/log";
import type { Collection } from "@/media/collection";
import { SearchResultsHeader } from "@/new/photos/components/Gallery";
import {
PeopleEmptyState,
SearchResultsHeader,
} from "@/new/photos/components/Gallery";
import type { GalleryBarMode } from "@/new/photos/components/Gallery/BarImpl";
import { GalleryPeopleState } from "@/new/photos/components/Gallery/PeopleHeader";
import {
SearchBar,
type SearchBarProps,
@@ -526,9 +530,16 @@ export default function Gallery() {
);
}, [collections, activeCollectionID]);
const filteredData = useMemoSingleThreaded(async (): Promise<
EnteFile[]
> => {
// The derived UI state when we are in "people" mode.
//
// TODO: This spawns even more workarounds below. Move this to a
// reducer/store.
type DerivedState1 = {
filteredData: EnteFile[];
galleryPeopleState: GalleryPeopleState | undefined;
};
const derived1: DerivedState1 = useMemoSingleThreaded(async () => {
if (
!files ||
!user ||
@@ -536,35 +547,56 @@ export default function Gallery() {
!hiddenFiles ||
!archivedCollections
) {
return;
return { filteredData: [], galleryPeopleState: undefined };
}
if (activeCollectionID === TRASH_SECTION && !selectedSearchOption) {
return getUniqueFiles([
const filteredData = getUniqueFiles([
...trashedFiles,
...files.filter((file) => tempDeletedFileIds?.has(file.id)),
]);
return { filteredData, galleryPeopleState: undefined };
}
let filteredFiles: EnteFile[] = [];
let galleryPeopleState: GalleryPeopleState;
if (selectedSearchOption) {
filteredFiles = await filterSearchableFiles(
selectedSearchOption.suggestion,
);
} else if (barMode == "people") {
const activePerson = ensure(
people.find((p) => p.id == activePersonID) ?? people[0],
);
const pfSet = new Set(activePerson.fileIDs);
let filteredPeople = people ?? [];
if (tempDeletedFileIds?.size ?? tempHiddenFileIds?.size) {
// Prune the in-memory temp updates from the actual state to
// obtain the UI state. Kept inside an preflight check to so
// that the common path remains fast.
filteredPeople = filteredPeople
.map((p) => ({
...p,
fileIDs: p.fileIDs.filter(
(id) =>
!tempDeletedFileIds?.has(id) &&
!tempHiddenFileIds?.has(id),
),
}))
.filter((p) => p.fileIDs.length > 0);
}
const activePerson =
filteredPeople.find((p) => p.id == activePersonID) ??
// We don't have an "All" pseudo-album in people mode currently,
// so default to the first person in the list.
filteredPeople[0];
const pfSet = new Set(activePerson?.fileIDs ?? []);
filteredFiles = getUniqueFiles(
files.filter(({ id }) => {
if (!pfSet.has(id)) return false;
// TODO-Cluster
// if (tempDeletedFileIds?.has(id)) return false;
// if (tempHiddenFileIds?.has(id)) return false;
return true;
}),
);
galleryPeopleState = {
activePerson,
people: filteredPeople,
};
} else {
const baseFiles = barMode == "hidden-albums" ? hiddenFiles : files;
filteredFiles = getUniqueFiles(
@@ -630,10 +662,10 @@ export default function Gallery() {
}
const sortAsc = activeCollection?.pubMagicMetadata?.data?.asc ?? false;
if (sortAsc) {
return sortFiles(filteredFiles, true);
} else {
return filteredFiles;
filteredFiles = sortFiles(filteredFiles, true);
}
return { filteredData: filteredFiles, galleryPeopleState };
}, [
barMode,
files,
@@ -649,6 +681,11 @@ export default function Gallery() {
activePersonID,
]);
const { filteredData, galleryPeopleState } = derived1 ?? {
filteredData: [],
galleryPeopleState: undefined,
};
const selectAll = (e: KeyboardEvent) => {
// ignore ctrl/cmd + a if the user is typing in a text field
if (
@@ -679,10 +716,13 @@ export default function Gallery() {
count: 0,
collectionID: activeCollectionID,
context:
barMode == "people" && activePersonID
? { mode: "people" as const, personID: activePersonID }
barMode == "people" && galleryPeopleState?.activePerson?.id
? {
mode: "people" as const,
personID: galleryPeopleState.activePerson.id,
}
: {
mode: "albums" as const,
mode: barMode as "albums" | "hidden-albums",
collectionID: ensure(activeCollectionID),
},
};
@@ -1053,12 +1093,12 @@ export default function Gallery() {
};
const handleSelectPerson = (person: Person | undefined) => {
// The person bar currently does not have an "all" mode, so default to
// the first person when no specific person is provided. This can happen
// when the user clicks the "People" header in the search empty state (it
// is guaranteed that this header will only be shown if there is at
// least one person).
setActivePersonID(person?.id ?? ensure(people[0]).id);
setActivePersonID(person?.id);
setBarMode("people");
};
const handleSelectFileInfoPerson = (personID: string) => {
setActivePersonID(personID);
setBarMode("people");
};
@@ -1066,6 +1106,10 @@ export default function Gallery() {
return <div></div>;
}
// `people` will be undefined only when ML is disabled, otherwise it'll be
// an empty array (even if people are loading).
const showPeopleSectionButton = people !== undefined;
return (
<GalleryContext.Provider
value={{
@@ -1170,8 +1214,9 @@ export default function Gallery() {
activeCollectionID,
setActiveCollectionID,
hiddenCollectionSummaries,
people,
activePersonID,
showPeopleSectionButton,
people: galleryPeopleState?.people ?? [],
activePerson: galleryPeopleState?.activePerson,
onSelectPerson: handleSelectPerson,
setCollectionNamerAttributes,
setPhotoListHeader,
@@ -1234,6 +1279,11 @@ export default function Gallery() {
!hiddenFiles?.length &&
activeCollectionID === ALL_SECTION ? (
<GalleryEmptyState openUploader={openUploader} />
) : !isInSearchMode &&
!isFirstLoad &&
barMode == "people" &&
!galleryPeopleState?.activePerson ? (
<PeopleEmptyState />
) : (
<PhotoFrame
page={PAGES.GALLERY}
@@ -1247,7 +1297,7 @@ export default function Gallery() {
setTempDeletedFileIds={setTempDeletedFileIds}
setIsPhotoSwipeOpen={setIsPhotoSwipeOpen}
activeCollectionID={activeCollectionID}
activePersonID={activePersonID}
activePersonID={galleryPeopleState?.activePerson?.id}
enableDownload={true}
fileToCollectionsMap={fileToCollectionsMap}
collectionNameMap={collectionNameMap}
@@ -1259,6 +1309,7 @@ export default function Gallery() {
setFilesDownloadProgressAttributesCreator
}
selectable={true}
onSelectPerson={handleSelectFileInfoPerson}
/>
)}
{selected.count > 0 &&

View File

@@ -318,9 +318,9 @@ const Slideshow: React.FC = () => {
/images/onboarding-lock/3x.png 3x"
/>
<FeatureText>
<Trans i18nKey={"HERO_SLIDE_1_TITLE"} />
<Trans i18nKey={"intro_slide_1_title"} />
</FeatureText>
<TextContainer>{t("HERO_SLIDE_1")}</TextContainer>
<TextContainer>{t("intro_slide_1")}</TextContainer>
</Slide>
<Slide index={1}>
<SlideContents>
@@ -330,9 +330,9 @@ const Slideshow: React.FC = () => {
/images/onboarding-safe/3x.png 3x"
/>
<FeatureText>
<Trans i18nKey={"HERO_SLIDE_2_TITLE"} />
<Trans i18nKey={"intro_slide_2_title"} />
</FeatureText>
<TextContainer>{t("HERO_SLIDE_2")}</TextContainer>
<TextContainer>{t("intro_slide_2")}</TextContainer>
</SlideContents>
</Slide>
<Slide index={2}>
@@ -343,9 +343,9 @@ const Slideshow: React.FC = () => {
/images/onboarding-sync/3x.png 3x"
/>
<FeatureText>
<Trans i18nKey={"HERO_SLIDE_3_TITLE"} />
<Trans i18nKey={"intro_slide_3_title"} />
</FeatureText>
<TextContainer>{t("HERO_SLIDE_3")}</TextContainer>
<TextContainer>{t("intro_slide_3")}</TextContainer>
</SlideContents>
</Slide>
</Slider>

View File

@@ -20,7 +20,7 @@ export const getDownloadAppMessage = (): DialogBoxAttributes => {
variant: "accent",
},
close: {
text: t("CLOSE"),
text: t("close"),
},
};
};
@@ -182,7 +182,7 @@ export const getEditorCloseConfirmationMessage = (
content: t("CONFIRM_EDITOR_CLOSE_DESCRIPTION"),
proceed: {
action: doClose,
text: t("CLOSE"),
text: t("close"),
variant: "critical",
},
close: { text: t("cancel") },

View File

@@ -99,7 +99,7 @@ const Page: React.FC<PageProps> = ({ appContext }) => {
const showNoRecoveryKeyMessage = () =>
setDialogBoxAttributesV2({
title: t("SORRY"),
title: t("sorry"),
close: {},
content: t("NO_RECOVERY_KEY_MESSAGE"),
});

View File

@@ -149,7 +149,7 @@ const Page: React.FC<RecoverPageProps> = ({ appContext, twoFactorType }) => {
dialogClose?: DialogBoxAttributesV2["close"],
) => {
appContext.setDialogBoxAttributesV2({
title: t("CONTACT_SUPPORT"),
title: t("contact_support"),
close: dialogClose ?? {},
content: (
<Trans

View File

@@ -33,6 +33,7 @@ import { t } from "i18next";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { Trans } from "react-i18next";
import { getSRPAttributes } from "../api/srp";
import { putAttributes, sendOtt, verifyOtt } from "../api/user";
import { PAGES } from "../constants/pages";
import {
@@ -42,7 +43,7 @@ import {
import { unstashRedirect } from "../services/redirect";
import { configureSRP } from "../services/srp";
import type { PageProps } from "../types/page";
import type { SRPSetupAttributes } from "../types/srp";
import type { SRPAttributes, SRPSetupAttributes } from "../types/srp";
const Page: React.FC<PageProps> = ({ appContext }) => {
const { logout, showNavBar, setDialogBoxAttributesV2 } = appContext;
@@ -58,16 +59,9 @@ const Page: React.FC<PageProps> = ({ appContext }) => {
useEffect(() => {
const main = async () => {
const user: User = getData(LS_KEYS.USER);
const keyAttributes: KeyAttributes = getData(
LS_KEYS.KEY_ATTRIBUTES,
);
if (!user?.email) {
router.push("/");
} else if (
keyAttributes?.encryptedKey &&
(user.token || user.encryptedToken)
) {
router.push(PAGES.CREDENTIALS);
const redirect = await redirectionIfNeeded(user);
if (redirect) {
router.push(redirect);
} else {
setEmail(user.email);
}
@@ -253,3 +247,45 @@ const Page: React.FC<PageProps> = ({ appContext }) => {
};
export default Page;
/**
* A function called during page load to see if a redirection is required
*
* @returns The slug to redirect to, if needed.
*/
const redirectionIfNeeded = async (user: User | undefined) => {
const email = user?.email;
if (!email) {
return "/";
}
const keyAttributes: KeyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
if (keyAttributes?.encryptedKey && (user.token || user.encryptedToken)) {
return PAGES.CREDENTIALS;
}
// The user might have email verification disabled, but after previously
// entering their email on the login screen, they might've closed the tab
// before proceeding (or opened a us in a new tab at this point).
//
// In such cases, we'll end up here with an email present.
//
// To distinguish this scenario from the normal email verification flow, we
// can check to see the SRP attributes (the login page would've fetched and
// saved them). If they are present and indicate that email verification is
// not required, redirect to the password verification page.
const srpAttributes: SRPAttributes = getData(LS_KEYS.SRP_ATTRIBUTES);
if (srpAttributes && !srpAttributes.isEmailMFAEnabled) {
// Fetch the latest SRP attributes instead of relying on the potentially
// stale stored values. This is an infrequent scenario path, so extra
// API calls are fine.
const latestSRPAttributes = await getSRPAttributes(email);
if (latestSRPAttributes && !latestSRPAttributes.isEmailMFAEnabled) {
return PAGES.CREDENTIALS;
}
}
return undefined;
};

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>نسخ احتياطية خاصة</div><div>لذكرياتك</div>",
"HERO_SLIDE_1": "تشفير من طرف إلى طرف بشكل افتراضي",
"HERO_SLIDE_2_TITLE": "<div>يتم تخزينها بأمان</div><div>في ملجأ للطوارئ</div>",
"HERO_SLIDE_2": "مصممة لتدوم",
"HERO_SLIDE_3_TITLE": "<div>متاح</div><div> في كل مكان</div>",
"HERO_SLIDE_3": "أندرويد، آي أو إس، ويب، سطح المكتب",
"intro_slide_1_title": "<div>نسخ احتياطية خاصة</div><div>لذكرياتك</div>",
"intro_slide_1": "تشفير من طرف إلى طرف بشكل افتراضي",
"intro_slide_2_title": "<div>يتم تخزينها بأمان</div><div>في ملجأ للطوارئ</div>",
"intro_slide_2": "مصممة لتدوم",
"intro_slide_3_title": "<div>متاح</div><div> في كل مكان</div>",
"intro_slide_3": "أندرويد، آي أو إس، ويب، سطح المكتب",
"login": "تسجيل الدخول",
"sign_up": "تسجيل",
"NEW_USER": "جديد في Ente",
"EXISTING_USER": "مستخدم موجود",
"ENTER_NAME": "أدخل الاسم",
"enter_name": "أدخل الاسم",
"PUBLIC_UPLOADER_NAME_MESSAGE": "أضف اسما حتى يتمكن أصدقاؤك من معرفة من يشكرون على هذه الصور الرائعة!",
"ENTER_EMAIL": "أدخل عنوان البريد الإلكتروني",
"EMAIL_ERROR": "أدخل بريد إلكتروني صالح",
@@ -44,11 +44,11 @@
"create_albums": "إنشاء ألبومات",
"CREATE_COLLECTION": "ألبوم جديد",
"enter_album_name": "اسم الألبوم",
"CLOSE_OPTION": "إغلاق (Esc)",
"close_key": "إغلاق (Esc)",
"enter_file_name": "إسم الملف",
"CLOSE": "إغلاق",
"NO": "لا",
"NOTHING_HERE": "",
"close": "إغلاق",
"no": "لا",
"nothing_here": "",
"upload": "تحميل",
"import": "استيراد",
"add_photos": "إضافة صور",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "تم تغيير كلمة المرور في مكان آخر",
"password_changed_elsewhere_message": "يرجى تسجيل الدخول مرة أخرى على هذا الجهاز لاستخدام كلمة المرور الجديدة للمصادقة.",
"GO_BACK": "رجوع",
"RECOVERY_KEY": "مفتاح الاستعادة",
"SAVE_LATER": "قم بهذا لاحقا",
"SAVE": "حفظ المفتاح",
"recovery_key": "مفتاح الاستعادة",
"do_this_later": "قم بهذا لاحقا",
"save_key": "حفظ المفتاح",
"RECOVERY_KEY_DESCRIPTION": "إذا نسيت كلمة المرور الخاصة بك، فالطريقة الوحيدة التي يمكنك بها استرداد البيانات الخاصة بك هي بهذا المفتاح.",
"RECOVER_KEY_GENERATION_FAILED": "لا يمكن إنشاء رمز الاسترداد، الرجاء المحاولة مرة أخرى",
"KEY_NOT_STORED_DISCLAIMER": "لا يتم تخزين هذا المفتاح أليا، لذا يرجى حفظ هذا في مكان آمن",
@@ -121,18 +121,17 @@
"RECOVER": "استعادة",
"NO_RECOVERY_KEY": "ما من مفتاح استعادة؟",
"INCORRECT_RECOVERY_KEY": "مفتاح استعادة غير صحيح",
"SORRY": "عذرا",
"sorry": "عذرا",
"NO_RECOVERY_KEY_MESSAGE": "بسبب طبيعة نظام التشفير التام بين الطرفين، لا يمكن فك تشفير بياناتك دون كلمة المرور أو مفتاح الاسترداد الخاص بك",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "الاتصال بالدعم",
"REQUEST_FEATURE": "طلب ميزة",
"SUPPORT": "الدعم",
"CONFIRM": "تأكيد",
"contact_support": "الاتصال بالدعم",
"request_feature": "طلب ميزة",
"support": "الدعم",
"cancel": "إلغاء",
"LOGOUT": "تسجيل الخروج",
"logout": "تسجيل الخروج",
"logout_message": "هل أنت متأكد من أنك تريد تسجيل الخروج؟",
"delete_account": "حذف الحساب",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "هل أنت متأكد من أنك تريد تسجيل الخروج؟",
"CHANGE_EMAIL": "تغيير البريد الإلكتروني",
"ok": "حسنا",
"success": "تم بنجاح",
@@ -324,7 +323,7 @@
"hide_collection": "إخفاء الألبوم",
"unhide_collection": "إلغاء إخفاء الألبوم",
"MOVE": "نقل",
"ADD": "إضافة",
"add": "إضافة",
"REMOVE": "ازالة",
"YES_REMOVE": "نعم، إزالة",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Личен бекъп</div><div>на твоите спомени</div>",
"HERO_SLIDE_1": "Криптиран от край до край по подразбиране",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "<div>Личен бекъп</div><div>на твоите спомени</div>",
"intro_slide_1": "Криптиран от край до край по подразбиране",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -0,0 +1,651 @@
{
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
"REQUIRED": "",
"EMAIL_SENT": "",
"CHECK_INBOX": "",
"ENTER_OTT": "",
"RESEND_MAIL": "",
"VERIFY": "",
"UNKNOWN_ERROR": "",
"INVALID_CODE": "",
"EXPIRED_CODE": "",
"SENDING": "",
"SENT": "",
"password": "",
"link_password_description": "",
"unlock": "",
"SET_PASSPHRASE": "",
"VERIFY_PASSPHRASE": "",
"INCORRECT_PASSPHRASE": "",
"ENTER_ENC_PASSPHRASE": "",
"PASSPHRASE_DISCLAIMER": "",
"WELCOME_TO_ENTE_HEADING": "",
"WELCOME_TO_ENTE_SUBHEADING": "",
"WHERE_YOUR_BEST_PHOTOS_LIVE": "",
"KEY_GENERATION_IN_PROGRESS_MESSAGE": "",
"PASSPHRASE_HINT": "",
"CONFIRM_PASSPHRASE": "",
"REFERRAL_CODE_HINT": "",
"REFERRAL_INFO": "",
"PASSPHRASE_MATCH_ERROR": "",
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"close_key": "",
"enter_file_name": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
"add_more_photos": "",
"add_photos_count_one": "",
"add_photos_count": "",
"select_photos": "",
"FILE_UPLOAD": "",
"UPLOAD_STAGE_MESSAGE": {
"0": "",
"1": "",
"2": "",
"3": "",
"4": "",
"5": ""
},
"FILE_NOT_UPLOADED_LIST": "",
"INITIAL_LOAD_DELAY_WARNING": "",
"USER_DOES_NOT_EXIST": "",
"NO_ACCOUNT": "",
"ACCOUNT_EXISTS": "",
"CREATE": "",
"download": "",
"download_album": "",
"download_favorites": "",
"download_uncategorized": "",
"download_hidden_items": "",
"download_key": "",
"copy_key": "",
"toggle_fullscreen_key": "",
"zoom_in_out_key": "",
"previous_key": "",
"next_key": "",
"title_photos": "",
"title_auth": "",
"title_accounts": "",
"UPLOAD_FIRST_PHOTO": "",
"IMPORT_YOUR_FOLDERS": "",
"UPLOAD_DROPZONE_MESSAGE": "",
"WATCH_FOLDER_DROPZONE_MESSAGE": "",
"TRASH_FILES_TITLE": "",
"TRASH_FILE_TITLE": "",
"DELETE_FILES_TITLE": "",
"DELETE_FILES_MESSAGE": "",
"DELETE": "",
"DELETE_OPTION": "",
"FAVORITE_OPTION": "",
"UNFAVORITE_OPTION": "",
"MULTI_FOLDER_UPLOAD": "",
"UPLOAD_STRATEGY_CHOICE": "",
"UPLOAD_STRATEGY_SINGLE_COLLECTION": "",
"OR": "",
"UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "",
"SESSION_EXPIRED_MESSAGE": "",
"SESSION_EXPIRED": "",
"PASSWORD_GENERATION_FAILED": "",
"CHANGE_PASSWORD": "",
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
"FORGOT_PASSWORD": "",
"RECOVER_ACCOUNT": "",
"RECOVERY_KEY_HINT": "",
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
"error": "",
"OFFLINE_MSG": "",
"install": "",
"install_mobile_app": "",
"download_app": "",
"download_app_message": "",
"EXPORT": "",
"SUBSCRIPTION": "",
"SUBSCRIBE": "",
"MANAGEMENT_PORTAL": "",
"MANAGE_FAMILY_PORTAL": "",
"LEAVE_FAMILY_PLAN": "",
"LEAVE": "",
"LEAVE_FAMILY_CONFIRM": "",
"CHOOSE_PLAN": "",
"MANAGE_PLAN": "",
"CURRENT_USAGE": "",
"TWO_MONTHS_FREE": "",
"POPULAR": "",
"free_plan_option": "",
"free_plan_description": "",
"active": "",
"subscription_info_free": "",
"subscription_info_family": "",
"subscription_info_expired": "",
"subscription_info_renewal_cancelled": "",
"subscription_info_storage_quota_exceeded": "",
"subscription_status_renewal_active": "",
"subscription_status_renewal_cancelled": "",
"add_on_valid_till": "",
"subscription_expired": "",
"storage_quota_exceeded": "",
"SUBSCRIPTION_PURCHASE_SUCCESS": "",
"SUBSCRIPTION_PURCHASE_CANCELLED": "",
"SUBSCRIPTION_PURCHASE_FAILED": "",
"SUBSCRIPTION_UPDATE_FAILED": "",
"UPDATE_PAYMENT_METHOD_MESSAGE": "",
"STRIPE_AUTHENTICATION_FAILED": "",
"UPDATE_PAYMENT_METHOD": "",
"MONTHLY": "",
"YEARLY": "",
"MONTH_SHORT": "",
"YEAR": "",
"update_subscription_title": "",
"UPDATE_SUBSCRIPTION_MESSAGE": "",
"UPDATE_SUBSCRIPTION": "",
"CANCEL_SUBSCRIPTION": "",
"CANCEL_SUBSCRIPTION_MESSAGE": "",
"CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "",
"SUBSCRIPTION_CANCEL_FAILED": "",
"SUBSCRIPTION_CANCEL_SUCCESS": "",
"REACTIVATE_SUBSCRIPTION": "",
"REACTIVATE_SUBSCRIPTION_MESSAGE": "",
"SUBSCRIPTION_ACTIVATE_SUCCESS": "",
"SUBSCRIPTION_ACTIVATE_FAILED": "",
"SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "",
"CANCEL_SUBSCRIPTION_ON_MOBILE": "",
"CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "",
"MAIL_TO_MANAGE_SUBSCRIPTION": "",
"rename": "",
"rename_file": "",
"rename_album": "",
"delete_album": "",
"delete_album_title": "",
"delete_album_message": "",
"delete_photos": "",
"keep_photos": "",
"share_album": "",
"SHARE_WITH_SELF": "",
"ALREADY_SHARED": "",
"SHARING_BAD_REQUEST_ERROR": "",
"SHARING_DISABLED_FOR_FREE_ACCOUNTS": "",
"CREATE_ALBUM_FAILED": "",
"search": "",
"search_results": "",
"no_results": "",
"search_hint": "",
"album": "",
"date": "",
"description": "",
"file_type": "",
"magic": "",
"photos_count_zero": "",
"photos_count_one": "",
"photos_count": "",
"TERMS_AND_CONDITIONS": "",
"SELECTED": "",
"people": "",
"indexing_scheduled": "",
"indexing_photos": "",
"indexing_fetching": "",
"indexing_people": "",
"indexing_done": "",
"UNIDENTIFIED_FACES": "",
"OBJECTS": "",
"TEXT": "",
"INFO": "",
"INFO_OPTION": "",
"file_name": "",
"CAPTION_PLACEHOLDER": "",
"location": "",
"SHOW_ON_MAP": "",
"MAP": "",
"MAP_SETTINGS": "",
"ENABLE_MAPS": "",
"ENABLE_MAP": "",
"DISABLE_MAPS": "",
"ENABLE_MAP_DESCRIPTION": "",
"DISABLE_MAP_DESCRIPTION": "",
"DISABLE_MAP": "",
"DETAILS": "",
"view_exif": "",
"no_exif": "",
"exif": "",
"ISO": "",
"TWO_FACTOR": "",
"TWO_FACTOR_AUTHENTICATION": "",
"TWO_FACTOR_QR_INSTRUCTION": "",
"ENTER_CODE_MANUALLY": "",
"TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "",
"SCAN_QR_CODE": "",
"ENABLE_TWO_FACTOR": "",
"enable": "",
"enabled": "",
"LOST_DEVICE": "",
"INCORRECT_CODE": "",
"TWO_FACTOR_INFO": "",
"DISABLE_TWO_FACTOR_LABEL": "",
"UPDATE_TWO_FACTOR_LABEL": "",
"disable": "",
"reconfigure": "",
"UPDATE_TWO_FACTOR": "",
"UPDATE_TWO_FACTOR_MESSAGE": "",
"UPDATE": "",
"DISABLE_TWO_FACTOR": "",
"DISABLE_TWO_FACTOR_MESSAGE": "",
"TWO_FACTOR_DISABLE_FAILED": "",
"EXPORT_DATA": "",
"select_folder": "",
"select_zips": "",
"faq": "",
"takeout_hint": "",
"DESTINATION": "",
"START": "",
"LAST_EXPORT_TIME": "",
"EXPORT_AGAIN": "",
"LOCAL_STORAGE_NOT_ACCESSIBLE": "",
"LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "",
"SEND_OTT": "",
"EMAIl_ALREADY_OWNED": "",
"ETAGS_BLOCKED": "",
"LIVE_PHOTOS_DETECTED": "",
"RETRY_FAILED": "",
"FAILED_UPLOADS": "",
"failed_uploads_hint": "",
"SKIPPED_FILES": "",
"THUMBNAIL_GENERATION_FAILED_UPLOADS": "",
"UNSUPPORTED_FILES": "",
"SUCCESSFUL_UPLOADS": "",
"SKIPPED_INFO": "",
"UNSUPPORTED_INFO": "",
"BLOCKED_UPLOADS": "",
"INPROGRESS_METADATA_EXTRACTION": "",
"INPROGRESS_UPLOADS": "",
"TOO_LARGE_UPLOADS": "",
"LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "",
"LARGER_THAN_AVAILABLE_STORAGE_INFO": "",
"TOO_LARGE_INFO": "",
"THUMBNAIL_GENERATION_FAILED_INFO": "",
"select_album": "",
"upload_to_album": "",
"add_to_album": "",
"move_to_album": "",
"unhide_to_album": "",
"restore_to_album": "",
"section_all": "",
"section_uncategorized": "",
"section_archive": "",
"section_hidden": "",
"section_trash": "",
"favorites": "",
"archive": "",
"archive_album": "",
"unarchive": "",
"unarchive_album": "",
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
"MOVE_TO_TRASH": "",
"TRASH_FILES_MESSAGE": "",
"TRASH_FILE_MESSAGE": "",
"DELETE_PERMANENTLY": "",
"RESTORE": "",
"empty_trash": "",
"empty_trash_title": "",
"empty_trash_message": "",
"leave_album": "",
"leave_shared_album_title": "",
"leave_shared_album_message": "",
"leave_shared_album": "",
"NOT_FILE_OWNER": "",
"CONFIRM_SELF_REMOVE_MESSAGE": "",
"CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "",
"sort_by_creation_time_ascending": "",
"sort_by_updation_time_descending": "",
"sort_by_name": "",
"FIX_CREATION_TIME": "",
"FIX_CREATION_TIME_IN_PROGRESS": "",
"CREATION_TIME_UPDATED": "",
"UPDATE_CREATION_TIME_NOT_STARTED": "",
"UPDATE_CREATION_TIME_COMPLETED": "",
"UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "",
"CAPTION_CHARACTER_LIMIT": "",
"DATE_TIME_ORIGINAL": "",
"DATE_TIME_DIGITIZED": "",
"METADATA_DATE": "",
"CUSTOM_TIME": "",
"REOPEN_PLAN_SELECTOR_MODAL": "",
"OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
"sharing_details": "",
"modify_sharing": "",
"ADD_COLLABORATORS": "",
"ADD_NEW_EMAIL": "",
"shared_with_people_count_zero": "",
"shared_with_people_count_one": "",
"shared_with_people_count": "",
"participants_count_zero": "",
"participants_count_one": "",
"participants_count": "",
"ADD_VIEWERS": "",
"CHANGE_PERMISSIONS_TO_VIEWER": "",
"CHANGE_PERMISSIONS_TO_COLLABORATOR": "",
"CONVERT_TO_VIEWER": "",
"CONVERT_TO_COLLABORATOR": "",
"CHANGE_PERMISSION": "",
"REMOVE_PARTICIPANT": "",
"CONFIRM_REMOVE": "",
"MANAGE": "",
"ADDED_AS": "",
"COLLABORATOR_RIGHTS": "",
"REMOVE_PARTICIPANT_HEAD": "",
"OWNER": "",
"COLLABORATORS": "",
"ADD_MORE": "",
"VIEWERS": "",
"OR_ADD_EXISTING": "",
"REMOVE_PARTICIPANT_MESSAGE": "",
"NOT_FOUND": "",
"LINK_EXPIRED": "",
"LINK_EXPIRED_MESSAGE": "",
"MANAGE_LINK": "",
"LINK_TOO_MANY_REQUESTS": "",
"FILE_DOWNLOAD": "",
"link_password_lock": "",
"PUBLIC_COLLECT": "",
"LINK_DEVICE_LIMIT": "",
"NO_DEVICE_LIMIT": "",
"LINK_EXPIRY": "",
"NEVER": "",
"DISABLE_FILE_DOWNLOAD": "",
"DISABLE_FILE_DOWNLOAD_MESSAGE": "",
"SHARED_USING": "",
"SHARING_REFERRAL_CODE": "",
"LIVE": "",
"DISABLE_PASSWORD": "",
"DISABLE_PASSWORD_MESSAGE": "",
"PASSWORD_LOCK": "",
"LOCK": "",
"file": "",
"folder": "",
"google_takeout": "",
"DEDUPLICATE_FILES": "",
"NO_DUPLICATES_FOUND": "",
"FILES": "",
"EACH": "",
"DEDUPLICATE_BASED_ON_SIZE": "",
"STOP_ALL_UPLOADS_MESSAGE": "",
"STOP_UPLOADS_HEADER": "",
"YES_STOP_UPLOADS": "",
"STOP_DOWNLOADS_HEADER": "",
"YES_STOP_DOWNLOADS": "",
"STOP_ALL_DOWNLOADS_MESSAGE": "",
"albums": "",
"albums_count_one": "",
"albums_count": "",
"all_albums": "",
"all_hidden_albums": "",
"hidden_albums": "",
"hidden_items": "",
"ENTER_TWO_FACTOR_OTP": "",
"CREATE_ACCOUNT": "",
"COPIED": "",
"WATCH_FOLDERS": "",
"upgrade_now": "",
"renew_now": "",
"STORAGE": "",
"USED": "",
"YOU": "",
"FAMILY": "",
"FREE": "",
"OF": "",
"WATCHED_FOLDERS": "",
"NO_FOLDERS_ADDED": "",
"FOLDERS_AUTOMATICALLY_MONITORED": "",
"UPLOAD_NEW_FILES_TO_ENTE": "",
"REMOVE_DELETED_FILES_FROM_ENTE": "",
"ADD_FOLDER": "",
"STOP_WATCHING": "",
"STOP_WATCHING_FOLDER": "",
"STOP_WATCHING_DIALOG_MESSAGE": "",
"YES_STOP": "",
"CHANGE_FOLDER": "",
"FAMILY_PLAN": "",
"debug_logs": "",
"DOWNLOAD_LOGS": "",
"DOWNLOAD_LOGS_MESSAGE": "",
"WEAK_DEVICE": "",
"drag_and_drop_hint": "",
"AUTHENTICATE": "",
"UPLOADED_TO_SINGLE_COLLECTION": "",
"UPLOADED_TO_SEPARATE_COLLECTIONS": "",
"NEVERMIND": "",
"UPDATE_AVAILABLE": "",
"UPDATE_INSTALLABLE_MESSAGE": "",
"INSTALL_NOW": "",
"INSTALL_ON_NEXT_LAUNCH": "",
"UPDATE_AVAILABLE_MESSAGE": "",
"DOWNLOAD_AND_INSTALL": "",
"IGNORE_THIS_VERSION": "",
"TODAY": "",
"YESTERDAY": "",
"NAME_PLACEHOLDER": "",
"ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "",
"ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "",
"CHOSE_THEME": "",
"more_details": "",
"ml_search": "",
"ml_search_description": "",
"ml_search_footnote": "",
"indexing": "",
"processed": "",
"indexing_status_running": "",
"indexing_status_fetching": "",
"indexing_status_scheduled": "",
"indexing_status_done": "",
"ml_search_disable": "",
"ml_search_disable_confirm": "",
"ml_consent": "",
"ml_consent_title": "",
"ml_consent_description": "",
"ml_consent_confirmation": "",
"labs": "",
"YOURS": "",
"passphrase_strength_weak": "",
"passphrase_strength_moderate": "",
"passphrase_strength_strong": "",
"preferences": "",
"language": "",
"advanced": "",
"EXPORT_DIRECTORY_DOES_NOT_EXIST": "",
"EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "",
"SUBSCRIPTION_VERIFICATION_ERROR": "",
"storage_unit": {
"b": "",
"kb": "",
"mb": "",
"gb": "",
"tb": ""
},
"AFTER_TIME": {
"HOUR": "",
"DAY": "",
"WEEK": "",
"MONTH": "",
"YEAR": ""
},
"COPY_LINK": "",
"DONE": "",
"LINK_SHARE_TITLE": "",
"REMOVE_LINK": "",
"CREATE_PUBLIC_SHARING": "",
"PUBLIC_LINK_CREATED": "",
"PUBLIC_LINK_ENABLED": "",
"COLLECT_PHOTOS": "",
"PUBLIC_COLLECT_SUBTEXT": "",
"STOP_EXPORT": "",
"EXPORT_PROGRESS": "",
"MIGRATING_EXPORT": "",
"RENAMING_COLLECTION_FOLDERS": "",
"TRASHING_DELETED_FILES": "",
"TRASHING_DELETED_COLLECTIONS": "",
"CONTINUOUS_EXPORT": "",
"PENDING_ITEMS": "",
"EXPORT_STARTING": "",
"delete_account_reason_label": "",
"delete_account_reason_placeholder": "",
"delete_reason": {
"missing_feature": "",
"behaviour": "",
"found_another_service": "",
"not_listed": ""
},
"delete_account_feedback_label": "",
"delete_account_feedback_placeholder": "",
"delete_account_confirm_checkbox_label": "",
"delete_account_confirm": "",
"delete_account_confirm_message": "",
"feedback_required": "",
"feedback_required_found_another_service": "",
"RECOVER_TWO_FACTOR": "",
"at": "",
"AUTH_NEXT": "",
"AUTH_DOWNLOAD_MOBILE_APP": "",
"HIDE": "",
"UNHIDE": "",
"sort_by": "",
"newest_first": "",
"oldest_first": "",
"CONVERSION_FAILED_NOTIFICATION_MESSAGE": "",
"pin_album": "",
"unpin_album": "",
"DOWNLOAD_COMPLETE": "",
"DOWNLOADING_COLLECTION": "",
"DOWNLOAD_FAILED": "",
"DOWNLOAD_PROGRESS": "",
"CHRISTMAS": "",
"CHRISTMAS_EVE": "",
"NEW_YEAR": "",
"NEW_YEAR_EVE": "",
"IMAGE": "",
"VIDEO": "",
"LIVE_PHOTO": "",
"editor": {
"crop": ""
},
"CONVERT": "",
"CONFIRM_EDITOR_CLOSE_MESSAGE": "",
"CONFIRM_EDITOR_CLOSE_DESCRIPTION": "",
"BRIGHTNESS": "",
"CONTRAST": "",
"SATURATION": "",
"BLUR": "",
"INVERT_COLORS": "",
"ASPECT_RATIO": "",
"SQUARE": "",
"ROTATE_LEFT": "",
"ROTATE_RIGHT": "",
"FLIP_VERTICALLY": "",
"FLIP_HORIZONTALLY": "",
"DOWNLOAD_EDITED": "",
"SAVE_A_COPY_TO_ENTE": "",
"RESTORE_ORIGINAL": "",
"TRANSFORM": "",
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",
"cast_album_to_tv": "",
"enter_cast_pin_code": "",
"pair_device_to_tv": "",
"tv_not_found": "",
"cast_auto_pair": "",
"cast_auto_pair_description": "",
"choose_device_from_browser": "",
"cast_auto_pair_failed": "",
"pair_with_pin": "",
"pair_with_pin_description": "",
"visit_cast_url": "",
"FREEHAND": "",
"APPLY_CROP": "",
"PHOTO_EDIT_REQUIRED_TO_SAVE": "",
"passkeys": "",
"passkey_fetch_failed": "",
"manage_passkey": "",
"delete_passkey": "",
"delete_passkey_confirmation": "",
"rename_passkey": "",
"add_passkey": "",
"enter_passkey_name": "",
"passkeys_description": "",
"CREATED_AT": "",
"passkey_add_failed": "",
"passkey_login_failed": "",
"passkey_login_invalid_url": "",
"passkey_login_already_claimed_session": "",
"passkey_login_generic_error": "",
"passkey_login_credential_hint": "",
"passkeys_not_supported": "",
"try_again": "",
"check_status": "",
"passkey_login_instructions": "",
"passkey_login": "",
"passkey": "",
"passkey_verify_description": "",
"waiting_for_verification": "",
"verification_still_pending": "",
"passkey_verified": "",
"redirecting_back_to_app": "",
"redirect_close_instructions": "",
"redirect_again": "",
"autogenerated_first_album_name": "",
"autogenerated_default_album_name": "",
"developer_settings": "",
"server_endpoint": "",
"more_information": "",
"save": ""
}

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Private Sicherungen</div><div>für deine Erinnerungen</div>",
"HERO_SLIDE_1": "Standardmäßig Ende-zu-Ende verschlüsselt",
"HERO_SLIDE_2_TITLE": "<div>Sicher gespeichert</div><div>in einem Luftschutzbunker</div>",
"HERO_SLIDE_2": "Entwickelt, um zu überleben",
"HERO_SLIDE_3_TITLE": "<div>Überall</div><div> verfügbar</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Desktop",
"intro_slide_1_title": "<div>Private Sicherungen</div><div>für deine Erinnerungen</div>",
"intro_slide_1": "Standardmäßig Ende-zu-Ende verschlüsselt",
"intro_slide_2_title": "<div>Sicher gespeichert</div><div>in einem Luftschutzbunker</div>",
"intro_slide_2": "Entwickelt, um zu überleben",
"intro_slide_3_title": "<div>Überall</div><div> verfügbar</div>",
"intro_slide_3": "Android, iOS, Web, Desktop",
"login": "Anmelden",
"sign_up": "Registrieren",
"NEW_USER": "Neu bei Ente",
"EXISTING_USER": "Existierender Benutzer",
"ENTER_NAME": "Name eingeben",
"enter_name": "Name eingeben",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Füge einen Namen hinzu, damit deine Freunde wissen, wem sie für diese tollen Fotos zu danken haben!",
"ENTER_EMAIL": "E-Mail-Adresse eingeben",
"EMAIL_ERROR": "Geben Sie eine gültige E-Mail-Adresse ein",
@@ -44,11 +44,11 @@
"create_albums": "Alben erstellen",
"CREATE_COLLECTION": "Neues Album",
"enter_album_name": "Albumname",
"CLOSE_OPTION": "Schließen (Esc)",
"close_key": "Schließen (Esc)",
"enter_file_name": "Dateiname",
"CLOSE": "Schließen",
"NO": "Nein",
"NOTHING_HERE": "",
"close": "Schließen",
"no": "Nein",
"nothing_here": "",
"upload": "Hochladen",
"import": "Importieren",
"add_photos": "Fotos hinzufügen",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Passwort anderswo geändert",
"password_changed_elsewhere_message": "Bitte melde dich erneut auf diesem Gerät an, um dein neues Passwort zur Authentifizierung zu verwenden.",
"GO_BACK": "Zurück",
"RECOVERY_KEY": "Wiederherstellungsschlüssel",
"SAVE_LATER": "Auf später verschieben",
"SAVE": "Schlüssel speichern",
"recovery_key": "Wiederherstellungsschlüssel",
"do_this_later": "Auf später verschieben",
"save_key": "Schlüssel speichern",
"RECOVERY_KEY_DESCRIPTION": "Falls du dein Passwort vergisst, kannst du deine Daten nur mit diesem Schlüssel wiederherstellen.",
"RECOVER_KEY_GENERATION_FAILED": "Wiederherstellungsschlüssel konnte nicht generiert werden, bitte versuche es erneut",
"KEY_NOT_STORED_DISCLAIMER": "Wir speichern diesen Schlüssel nicht, also speichere ihn bitte an einem sicheren Ort",
@@ -121,18 +121,17 @@
"RECOVER": "Wiederherstellen",
"NO_RECOVERY_KEY": "Kein Wiederherstellungsschlüssel?",
"INCORRECT_RECOVERY_KEY": "Falscher Wiederherstellungs-Schlüssel",
"SORRY": "Entschuldigung",
"sorry": "Entschuldigung",
"NO_RECOVERY_KEY_MESSAGE": "Aufgrund unseres Ende-zu-Ende-Verschlüsselungsprotokolls können Ihre Daten nicht ohne Ihr Passwort oder Ihren Wiederherstellungsschlüssel entschlüsselt werden",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Bitte sende eine E-Mail an <a>{{emailID}}</a> von deiner registrierten E-Mail-Adresse",
"CONTACT_SUPPORT": "Support kontaktieren",
"REQUEST_FEATURE": "Feature anfragen",
"SUPPORT": "Support",
"CONFIRM": "Bestätigen",
"contact_support": "Support kontaktieren",
"request_feature": "Feature anfragen",
"support": "Support",
"cancel": "Abbrechen",
"LOGOUT": "Ausloggen",
"logout": "Ausloggen",
"logout_message": "Sind sie sicher, dass sie sich ausloggen möchten?",
"delete_account": "Konto löschen",
"delete_account_manually_message": "<p>Bitte sende eine E-Mail an <a>{{emailID}}</a> mit deiner registrierten E-Mail-Adresse.</p><p>Deine Anfrage wird innerhalb von 72 Stunden bearbeitet.</p>",
"LOGOUT_MESSAGE": "Sind sie sicher, dass sie sich ausloggen möchten?",
"CHANGE_EMAIL": "E-Mail-Adresse ändern",
"ok": "OK",
"success": "Erfolgreich",
@@ -324,7 +323,7 @@
"hide_collection": "Album ausblenden",
"unhide_collection": "Album wieder einblenden",
"MOVE": "Verschieben",
"ADD": "Hinzufügen",
"add": "Hinzufügen",
"REMOVE": "Entfernen",
"YES_REMOVE": "Ja, entfernen",
"REMOVE_FROM_COLLECTION": "Aus Album entfernen",
@@ -596,7 +595,7 @@
"COLORS": "Farben",
"FLIP": "Spiegeln",
"ROTATION": "Drehen",
"RESET": "Zurücksetzen",
"reset": "Zurücksetzen",
"PHOTO_EDITOR": "Foto-Editor",
"FASTER_UPLOAD": "Schnelleres Hochladen",
"FASTER_UPLOAD_DESCRIPTION": "Uploads über nahegelegene Server leiten",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Ιδιωτικά αντίγραφα ασφαλείας</div><div>για τις αναμνήσεις σας</div>",
"HERO_SLIDE_1": "Από προεπιλογή κρυπτογραφημένο από άκρο σε άκρο",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "Σχεδιάστηκε για να επιζήσει",
"HERO_SLIDE_3_TITLE": "<div>Διαθέσιμο</div><div> παντού</div>",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "<div>Ιδιωτικά αντίγραφα ασφαλείας</div><div>για τις αναμνήσεις σας</div>",
"intro_slide_1": "Από προεπιλογή κρυπτογραφημένο από άκρο σε άκρο",
"intro_slide_2_title": "",
"intro_slide_2": "Σχεδιάστηκε για να επιζήσει",
"intro_slide_3_title": "<div>Διαθέσιμο</div><div> παντού</div>",
"intro_slide_3": "",
"login": "Σύνδεση",
"sign_up": "Εγγραφή",
"NEW_USER": "Νέος/α στο Ente",
"EXISTING_USER": "Υπάρχων χρήστης",
"ENTER_NAME": "Εισάγετε όνομα",
"enter_name": "Εισάγετε όνομα",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Προσθέστε ένα όνομα, ώστε οι φίλοι σας να γνωρίζουν ποιον να ευχαριστήσουν για αυτές τις υπέροχες φωτογραφίες!",
"ENTER_EMAIL": "Εισάγετε διεύθυνση ηλ. ταχυδρομείου",
"EMAIL_ERROR": "Εισάγετε μία έγκυρη διεύθυνση ηλ. ταχυδρομείου",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "Νέο άλμπουμ",
"enter_album_name": "Όνομα άλμπουμ",
"CLOSE_OPTION": "Κλείσιμο (Esc)",
"close_key": "Κλείσιμο (Esc)",
"enter_file_name": "Όνομα αρχείου",
"CLOSE": "Κλείσιμο",
"NO": "Όχι",
"NOTHING_HERE": "",
"close": "Κλείσιμο",
"no": "Όχι",
"nothing_here": "",
"upload": "Μεταφόρτωση",
"import": "Εισαγωγή",
"add_photos": "Προσθήκη φωτογραφιών",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "Παρακαλώ συνδεθείτε ξανά σε αυτήν τη συσκευή για να χρησιμοποιήσετε το νέο σας κωδικό πρόσβασης για αυθεντικοποίηση.",
"GO_BACK": "Επιστροφή",
"RECOVERY_KEY": "Κλειδί ανάκτησης",
"SAVE_LATER": "Κάντε το αργότερα",
"SAVE": "Αποθήκευση Κλειδιού",
"recovery_key": "Κλειδί ανάκτησης",
"do_this_later": "Κάντε το αργότερα",
"save_key": "Αποθήκευση Κλειδιού",
"RECOVERY_KEY_DESCRIPTION": "Εάν ξεχάσετε τον κωδικό πρόσβασής σας, ο μόνος τρόπος για να ανακτήσετε τα δεδομένα σας είναι με αυτό το κλειδί.",
"RECOVER_KEY_GENERATION_FAILED": "Δεν ήταν δυνατή η δημιουργία κωδικού ανάκτησης, παρακαλώ προσπαθήστε ξανά",
"KEY_NOT_STORED_DISCLAIMER": "Δεν αποθηκεύουμε αυτό το κλειδί, οπότε παρακαλώ αποθηκεύστε αυτό το κλειδί σε μια ασφαλή τοποθεσία",
@@ -121,18 +121,17 @@
"RECOVER": "Ανάκτηση",
"NO_RECOVERY_KEY": "Χωρίς κλειδί ανάκτησης;",
"INCORRECT_RECOVERY_KEY": "Εσφαλμένο κλειδί ανάκτησης",
"SORRY": "Συγνώμη",
"sorry": "Συγνώμη",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Παρακαλώ αφήστε ένα μήνυμα ηλ. ταχυδρομείου στο <a>{{emailID}}</a> από την καταχωρημένη διεύθυνση σας",
"CONTACT_SUPPORT": "Επικοινωνήστε με την υποστήριξη",
"REQUEST_FEATURE": "Αίτηση Λειτουργίας",
"SUPPORT": "Υποστήριξη",
"CONFIRM": "Επιβεβαίωση",
"contact_support": "Επικοινωνήστε με την υποστήριξη",
"request_feature": "Αίτηση Λειτουργίας",
"support": "Υποστήριξη",
"cancel": "Ακύρωση",
"LOGOUT": "Αποσυνδέση",
"logout": "Αποσυνδέση",
"logout_message": "Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε;",
"delete_account": "Διαγραφή λογαριασμού",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε;",
"CHANGE_EMAIL": "Αλλαγή διεύθυνσης ηλ. ταχυδρομείου",
"ok": "ΟΚ",
"success": "Επιτυχία",
@@ -324,7 +323,7 @@
"hide_collection": "Απόκρυψη άλμπουμ",
"unhide_collection": "Επανεμφάνιση άλμπουμ",
"MOVE": "Μετακίνηση",
"ADD": "Προσθήκη",
"add": "Προσθήκη",
"REMOVE": "Αφαίρεση",
"YES_REMOVE": "Ναι, αφαίρεση",
"REMOVE_FROM_COLLECTION": "Αφαίρεση από το άλμπουμ",
@@ -596,7 +595,7 @@
"COLORS": "Χρώματα",
"FLIP": "",
"ROTATION": "Περιστροφή",
"RESET": "Επαναφορά",
"reset": "Επαναφορά",
"PHOTO_EDITOR": "Επεξεργαστής φωτογραφιών",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Private backups</div><div>for your memories</div>",
"HERO_SLIDE_1": "End-to-end encrypted by default",
"HERO_SLIDE_2_TITLE": "<div>Safely stored</div><div>at a fallout shelter</div>",
"HERO_SLIDE_2": "Designed to outlive",
"HERO_SLIDE_3_TITLE": "<div>Available</div><div> everywhere</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Desktop",
"intro_slide_1_title": "<div>Private backups</div><div>for your memories</div>",
"intro_slide_1": "End-to-end encrypted by default",
"intro_slide_2_title": "<div>Safely stored</div><div>at a fallout shelter</div>",
"intro_slide_2": "Designed to outlive",
"intro_slide_3_title": "<div>Available</div><div> everywhere</div>",
"intro_slide_3": "Android, iOS, Web, Desktop",
"login": "Login",
"sign_up": "Signup",
"NEW_USER": "New to Ente",
"EXISTING_USER": "Existing user",
"ENTER_NAME": "Enter name",
"enter_name": "Enter name",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Add a name so that your friends know who to thank for these great photos!",
"ENTER_EMAIL": "Enter email address",
"EMAIL_ERROR": "Enter a valid email",
@@ -44,11 +44,11 @@
"create_albums": "Create albums",
"CREATE_COLLECTION": "New album",
"enter_album_name": "Album name",
"CLOSE_OPTION": "Close (Esc)",
"close_key": "Close (Esc)",
"enter_file_name": "File name",
"CLOSE": "Close",
"NO": "No",
"NOTHING_HERE": "Nothing here yet",
"close": "Close",
"no": "No",
"nothing_here": "Nothing here yet",
"upload": "Upload",
"import": "Import",
"add_photos": "Add photos",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Password changed elsewhere",
"password_changed_elsewhere_message": "Please login again on this device to use your new password to authenticate.",
"GO_BACK": "Go back",
"RECOVERY_KEY": "Recovery key",
"SAVE_LATER": "Do this later",
"SAVE": "Save Key",
"recovery_key": "Recovery key",
"do_this_later": "Do this later",
"save_key": "Save Key",
"RECOVERY_KEY_DESCRIPTION": "If you forget your password, the only way you can recover your data is with this key.",
"RECOVER_KEY_GENERATION_FAILED": "Recovery code could not be generated, please try again",
"KEY_NOT_STORED_DISCLAIMER": "We don't store this key, so please save this in a safe place",
@@ -121,18 +121,17 @@
"RECOVER": "Recover",
"NO_RECOVERY_KEY": "No recovery key?",
"INCORRECT_RECOVERY_KEY": "Incorrect recovery key",
"SORRY": "Sorry",
"sorry": "Sorry",
"NO_RECOVERY_KEY_MESSAGE": "Due to the nature of our end-to-end encryption protocol, your data cannot be decrypted without your password or recovery key",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Please drop an email to <a>{{emailID}}</a> from your registered email address",
"CONTACT_SUPPORT": "Contact support",
"REQUEST_FEATURE": "Request Feature",
"SUPPORT": "Support",
"CONFIRM": "Confirm",
"contact_support": "Contact support",
"request_feature": "Request Feature",
"support": "Support",
"cancel": "Cancel",
"LOGOUT": "Logout",
"logout": "Logout",
"logout_message": "Are you sure you want to logout?",
"delete_account": "Delete account",
"delete_account_manually_message": "<p>Please send an email to <a>{{emailID}}</a> from your registered email address.</p><p>Your request will be processed within 72 hours.</p>",
"LOGOUT_MESSAGE": "Are you sure you want to logout?",
"CHANGE_EMAIL": "Change email",
"ok": "OK",
"success": "Success",
@@ -324,7 +323,7 @@
"hide_collection": "Hide album",
"unhide_collection": "Unhide album",
"MOVE": "Move",
"ADD": "Add",
"add": "Add",
"REMOVE": "Remove",
"YES_REMOVE": "Yes, remove",
"REMOVE_FROM_COLLECTION": "Remove from album",
@@ -596,7 +595,7 @@
"COLORS": "Colors",
"FLIP": "Flip",
"ROTATION": "Rotation",
"RESET": "Reset",
"reset": "Reset",
"PHOTO_EDITOR": "Photo Editor",
"FASTER_UPLOAD": "Faster uploads",
"FASTER_UPLOAD_DESCRIPTION": "Route uploads through nearby servers",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Copias de seguridad privadas</div><div>para su recuerdos</div>",
"HERO_SLIDE_1": "Encriptado de extremo a extremo por defecto",
"HERO_SLIDE_2_TITLE": "<div>Almacenado de forma segura</div><div>en un refugio de llenos</div>",
"HERO_SLIDE_2": "Diseñado para superar",
"HERO_SLIDE_3_TITLE": "<div>Disponible</div><div> en todas partes</div>",
"HERO_SLIDE_3": "Android, iOS, web, computadora",
"intro_slide_1_title": "<div>Copias de seguridad privadas</div><div>para su recuerdos</div>",
"intro_slide_1": "Encriptado de extremo a extremo por defecto",
"intro_slide_2_title": "<div>Almacenado de forma segura</div><div>en un refugio de llenos</div>",
"intro_slide_2": "Diseñado para superar",
"intro_slide_3_title": "<div>Disponible</div><div> en todas partes</div>",
"intro_slide_3": "Android, iOS, web, computadora",
"login": "Conectar",
"sign_up": "Registro",
"NEW_USER": "Nuevo en Ente",
"EXISTING_USER": "Usuario existente",
"ENTER_NAME": "Introducir nombre",
"enter_name": "Introducir nombre",
"PUBLIC_UPLOADER_NAME_MESSAGE": "¡Añade un nombre para que tus amigos sepan a quién dar las gracias por estas fotos geniales!",
"ENTER_EMAIL": "Introducir email",
"EMAIL_ERROR": "Introduce un email válido",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "Nuevo álbum",
"enter_album_name": "Nombre del álbum",
"CLOSE_OPTION": "Cerrar (Esc)",
"close_key": "Cerrar (Esc)",
"enter_file_name": "Nombre del archivo",
"CLOSE": "Cerrar",
"NO": "No",
"NOTHING_HERE": "",
"close": "Cerrar",
"no": "No",
"nothing_here": "",
"upload": "Cargar",
"import": "Importar",
"add_photos": "Añadir fotos",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "Retroceder",
"RECOVERY_KEY": "Clave de recuperación",
"SAVE_LATER": "Hacer más tarde",
"SAVE": "Guardar Clave",
"recovery_key": "Clave de recuperación",
"do_this_later": "Hacer más tarde",
"save_key": "Guardar Clave",
"RECOVERY_KEY_DESCRIPTION": "Si olvida su contraseña, la única forma de recuperar sus datos es con esta clave.",
"RECOVER_KEY_GENERATION_FAILED": "El código de recuperación no pudo ser generado, por favor inténtalo de nuevo",
"KEY_NOT_STORED_DISCLAIMER": "No almacenamos esta clave, así que por favor guarde esto en un lugar seguro",
@@ -121,18 +121,17 @@
"RECOVER": "Recuperar",
"NO_RECOVERY_KEY": "No hay clave de recuperación?",
"INCORRECT_RECOVERY_KEY": "Clave de recuperación incorrecta",
"SORRY": "Lo sentimos",
"sorry": "Lo sentimos",
"NO_RECOVERY_KEY_MESSAGE": "Debido a la naturaleza de nuestro protocolo de cifrado de extremo a extremo, sus datos no pueden ser descifrados sin su contraseña o clave de recuperación",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Por favor, envíe un email a <a>{{emailID}}</a> desde su dirección de correo electrónico registrada",
"CONTACT_SUPPORT": "Contacta con soporte",
"REQUEST_FEATURE": "Solicitar una función",
"SUPPORT": "Soporte",
"CONFIRM": "Confirmar",
"contact_support": "Contacta con soporte",
"request_feature": "Solicitar una función",
"support": "Soporte",
"cancel": "Cancelar",
"LOGOUT": "Cerrar sesión",
"logout": "Cerrar sesión",
"logout_message": "Seguro que quiere cerrar la sesión?",
"delete_account": "Eliminar cuenta",
"delete_account_manually_message": "<p>Por favor, envíe un email a <a>{{emailID}}</a> desde su dirección de correo electrónico registrada</p><p>Su solicitud será procesada en 72 horas.</p>",
"LOGOUT_MESSAGE": "Seguro que quiere cerrar la sesión?",
"CHANGE_EMAIL": "Cambiar email",
"ok": "OK",
"success": "Completado",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "Mover",
"ADD": "Añadir",
"add": "Añadir",
"REMOVE": "Eliminar",
"YES_REMOVE": "Sí, eliminar",
"REMOVE_FROM_COLLECTION": "Eliminar del álbum",
@@ -596,7 +595,7 @@
"COLORS": "Colores",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Yksityiset varmuuskopiot</div><div>muistoillesi</div>",
"HERO_SLIDE_1": "Päästä päähän -salaus käytössä oletuksena",
"HERO_SLIDE_2_TITLE": "<div>Turvallisesti varastoitu</div><div>väestönsuojan tiloissa</div>",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "<div>Saatavilla</div><div> kaikkialla</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Tietokone",
"intro_slide_1_title": "<div>Yksityiset varmuuskopiot</div><div>muistoillesi</div>",
"intro_slide_1": "Päästä päähän -salaus käytössä oletuksena",
"intro_slide_2_title": "<div>Turvallisesti varastoitu</div><div>väestönsuojan tiloissa</div>",
"intro_slide_2": "",
"intro_slide_3_title": "<div>Saatavilla</div><div> kaikkialla</div>",
"intro_slide_3": "Android, iOS, Web, Tietokone",
"login": "Kirjaudu sisään",
"sign_up": "Rekisteröidy",
"NEW_USER": "Uusi Ente-käyttäjä",
"EXISTING_USER": "Jo valmiiksi olemassaoleva käyttäjä",
"ENTER_NAME": "Lisää nimi",
"enter_name": "Lisää nimi",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Lisää nimi, jotta ystäväsi tietävät, ketä kiittää näistä hienoista kuvista!",
"ENTER_EMAIL": "Syötä sähköpostiosoite",
"EMAIL_ERROR": "Syötä voimassa oleva sähköpostiosoite",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "Uusi albumi",
"enter_album_name": "Albumin nimi",
"CLOSE_OPTION": "Sulje (Esc)",
"close_key": "Sulje (Esc)",
"enter_file_name": "Tiedoston nimi",
"CLOSE": "Sulje",
"NO": "Ei",
"NOTHING_HERE": "",
"close": "Sulje",
"no": "Ei",
"nothing_here": "",
"upload": "Lataa",
"import": "Tuo",
"add_photos": "Lisää kuvia",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Sauvegardes privées</div><div>pour vos souvenirs</div>",
"HERO_SLIDE_1": "Chiffrement de bout en bout par défaut",
"HERO_SLIDE_2_TITLE": "<div>Sécurisé </div><div>dans un abri antiatomique</div>",
"HERO_SLIDE_2": "Conçu pour survivre",
"HERO_SLIDE_3_TITLE": "<div>Disponible</div><div> en tout lieu</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Ordinateur",
"intro_slide_1_title": "<div>Sauvegardes privées</div><div>pour vos souvenirs</div>",
"intro_slide_1": "Chiffrement de bout en bout par défaut",
"intro_slide_2_title": "<div>Sécurisé </div><div>dans un abri antiatomique</div>",
"intro_slide_2": "Conçu pour survivre",
"intro_slide_3_title": "<div>Disponible</div><div> en tout lieu</div>",
"intro_slide_3": "Android, iOS, Web, Ordinateur",
"login": "Connexion",
"sign_up": "Inscription",
"NEW_USER": "Nouveau sur Ente",
"EXISTING_USER": "Utilisateur existant",
"ENTER_NAME": "Saisir un nom",
"enter_name": "Saisir un nom",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Ajouter un nom afin que vos amis sachent qui remercier pour ces magnifiques photos!",
"ENTER_EMAIL": "Saisir l'adresse e-mail",
"EMAIL_ERROR": "Saisir un e-mail valide",
@@ -44,11 +44,11 @@
"create_albums": "Créer des albums",
"CREATE_COLLECTION": "Nouvel album",
"enter_album_name": "Nom de l'album",
"CLOSE_OPTION": "Fermer (Échap)",
"close_key": "Fermer (Échap)",
"enter_file_name": "Nom du fichier",
"CLOSE": "Fermer",
"NO": "Non",
"NOTHING_HERE": "",
"close": "Fermer",
"no": "Non",
"nothing_here": "",
"upload": "Charger",
"import": "Importer",
"add_photos": "Ajouter des photos",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Mot de passe modifié ailleurs",
"password_changed_elsewhere_message": "Veuillez vous reconnecter sur cet appareil pour utiliser votre nouveau mot de passe pour vous authentifier.",
"GO_BACK": "Retour",
"RECOVERY_KEY": "Clé de récupération",
"SAVE_LATER": "Plus tard",
"SAVE": "Sauvegarder la clé",
"recovery_key": "Clé de récupération",
"do_this_later": "Plus tard",
"save_key": "Sauvegarder la clé",
"RECOVERY_KEY_DESCRIPTION": "Si vous oubliez votre mot de passe, la seule façon de récupérer vos données sera grâce à cette clé.",
"RECOVER_KEY_GENERATION_FAILED": "Le code de récupération ne peut être généré, veuillez réessayer",
"KEY_NOT_STORED_DISCLAIMER": "Nous ne stockons pas cette clé, veuillez donc la sauvegarder dans un endroit sûr",
@@ -121,18 +121,17 @@
"RECOVER": "Récupérer",
"NO_RECOVERY_KEY": "Pas de clé de récupération?",
"INCORRECT_RECOVERY_KEY": "Clé de récupération non valide",
"SORRY": "Désolé",
"sorry": "Désolé",
"NO_RECOVERY_KEY_MESSAGE": "En raison de notre protocole de chiffrement de bout en bout, vos données ne peuvent être décryptées sans votre mot de passe ou clé de récupération",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Veuillez envoyer un e-mail à <a>{{emailID}}</a> depuis votre adresse enregistrée",
"CONTACT_SUPPORT": "Contacter le support",
"REQUEST_FEATURE": "Soumettre une idée",
"SUPPORT": "Support",
"CONFIRM": "Confirmer",
"contact_support": "Contacter le support",
"request_feature": "Soumettre une idée",
"support": "Support",
"cancel": "Annuler",
"LOGOUT": "Déconnexion",
"logout": "Déconnexion",
"logout_message": "Voulez-vous vraiment vous déconnecter?",
"delete_account": "Supprimer le compte",
"delete_account_manually_message": "<p>Veuillez envoyer un e-mail à <a>{{emailID}}</a>depuis Votre adresse enregistrée.</p><p> Votre demande sera traitée dans les 72 heures.</p>",
"LOGOUT_MESSAGE": "Voulez-vous vraiment vous déconnecter?",
"CHANGE_EMAIL": "Modifier l'e-mail",
"ok": "Ok",
"success": "Parfait",
@@ -324,7 +323,7 @@
"hide_collection": "Masquer l'album",
"unhide_collection": "Dévoiler l'album",
"MOVE": "Déplacer",
"ADD": "Ajouter",
"add": "Ajouter",
"REMOVE": "Retirer",
"YES_REMOVE": "Oui, retirer",
"REMOVE_FROM_COLLECTION": "Retirer de l'album",
@@ -596,7 +595,7 @@
"COLORS": "Couleurs",
"FLIP": "Retourner",
"ROTATION": "Rotation",
"RESET": "Réinitialiser",
"reset": "Réinitialiser",
"PHOTO_EDITOR": "Éditeur de photos",
"FASTER_UPLOAD": "Chargements plus rapides",
"FASTER_UPLOAD_DESCRIPTION": "Router les chargements vers les serveurs à proximité",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Cadangan pribadi</div><div>untuk kenanganmu</div>",
"HERO_SLIDE_1": "Dirancang dengan enkripsi ujung ke ujung",
"HERO_SLIDE_2_TITLE": "<div>Tersimpan aman</div><div>di tempat pengungsian</div>",
"HERO_SLIDE_2": "Dibuat untuk melestarikan",
"HERO_SLIDE_3_TITLE": "<div>Tersedia</div><div> di mana saja</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Desktop",
"intro_slide_1_title": "<div>Cadangan pribadi</div><div>untuk kenanganmu</div>",
"intro_slide_1": "Dirancang dengan enkripsi ujung ke ujung",
"intro_slide_2_title": "<div>Tersimpan aman</div><div>di tempat pengungsian</div>",
"intro_slide_2": "Dibuat untuk melestarikan",
"intro_slide_3_title": "<div>Tersedia</div><div> di mana saja</div>",
"intro_slide_3": "Android, iOS, Web, Desktop",
"login": "Masuk",
"sign_up": "Daftar",
"NEW_USER": "Baru di Ente",
"EXISTING_USER": "Pengguna yang sudah ada",
"ENTER_NAME": "Masukkan nama",
"enter_name": "Masukkan nama",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Tambahkan nama agar teman Anda tahu kepada siapa harus berterima kasih atas foto-foto hebat ini!",
"ENTER_EMAIL": "Masukkan alamat email",
"EMAIL_ERROR": "Masukkan email yang sah",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "Album baru",
"enter_album_name": "Nama album",
"CLOSE_OPTION": "Tutup (Esc)",
"close_key": "Tutup (Esc)",
"enter_file_name": "Nama file",
"CLOSE": "Tutup",
"NO": "Tidak",
"NOTHING_HERE": "",
"close": "Tutup",
"no": "Tidak",
"nothing_here": "",
"upload": "Unggah",
"import": "Impor",
"add_photos": "Tambahkan foto",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "Kembali",
"RECOVERY_KEY": "Kunci pemulihan",
"SAVE_LATER": "Lakukan lain kali",
"SAVE": "Simpan Kunci",
"recovery_key": "Kunci pemulihan",
"do_this_later": "Lakukan lain kali",
"save_key": "Simpan Kunci",
"RECOVERY_KEY_DESCRIPTION": "Jika Anda lupa kata sandi, satu-satunya cara memulihkan data Anda adalah dengan kunci ini.",
"RECOVER_KEY_GENERATION_FAILED": "Tidak dapat menghasilkan kode pemulihan, silakan coba lagi",
"KEY_NOT_STORED_DISCLAIMER": "Kami tidak menyimpan kunci ini, jadi harap simpan kunci ini dengan aman",
@@ -121,18 +121,17 @@
"RECOVER": "Pulihkan",
"NO_RECOVERY_KEY": "Tidak punya kunci pemulihan?",
"INCORRECT_RECOVERY_KEY": "Kunci pemulihan salah",
"SORRY": "Maaf",
"sorry": "Maaf",
"NO_RECOVERY_KEY_MESSAGE": "Karena sifat protokol enkripsi ujung ke ujung kami, data kamu tidak dapat didekripsi tanpa sandi atau kunci pemulihan kamu",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Silakan kirimkan email ke <a>{{emailID}}</a> dari alamat email terdaftar kamu",
"CONTACT_SUPPORT": "Hubungi dukungan",
"REQUEST_FEATURE": "Minta Fitur",
"SUPPORT": "Dukungan",
"CONFIRM": "Konfirmasi",
"contact_support": "Hubungi dukungan",
"request_feature": "Minta Fitur",
"support": "Dukungan",
"cancel": "Batal",
"LOGOUT": "Keluar akun",
"logout": "Keluar akun",
"logout_message": "Apakah kamu yakin ingin keluar akun?",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "Apakah kamu yakin ingin keluar akun?",
"CHANGE_EMAIL": "Ubah email",
"ok": "OK",
"success": "Berhasil",
@@ -324,7 +323,7 @@
"hide_collection": "Sembunyikan album",
"unhide_collection": "",
"MOVE": "Pindahkan",
"ADD": "Tambah",
"add": "Tambah",
"REMOVE": "Hapus",
"YES_REMOVE": "Ya, hapus",
"REMOVE_FROM_COLLECTION": "Hapus dari album",
@@ -596,7 +595,7 @@
"COLORS": "Warna",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "Editor Foto",
"FASTER_UPLOAD": "Pengunggahan lebih cepat",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "Loka",
"NO": "Nei",
"NOTHING_HERE": "",
"close": "Loka",
"no": "Nei",
"nothing_here": "",
"upload": "Hlaða upp",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "Fara til baka",
"RECOVERY_KEY": "",
"SAVE_LATER": "Gera þetta seinna",
"SAVE": "Vista Lykil",
"recovery_key": "",
"do_this_later": "Gera þetta seinna",
"save_key": "Vista Lykil",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "Endurheimta",
"NO_RECOVERY_KEY": "Enginn endurheimtunarlykill?",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "Fyrirgefðu",
"sorry": "Fyrirgefðu",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "Staðfesta",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "Hætta við",
"LOGOUT": "Útskrá",
"logout": "Útskrá",
"logout_message": "Ertu viss um að þú viljir skrá þig út?",
"delete_account": "Eyða aðgangi",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "Ertu viss um að þú viljir skrá þig út?",
"CHANGE_EMAIL": "Breyta netfangi",
"ok": "Í lagi",
"success": "Tókst",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Backup privati</div><div>dei tuoi ricordi</div>",
"HERO_SLIDE_1": "Crittografia end-to-end",
"HERO_SLIDE_2_TITLE": "<div>Salvati in modo sicuro</div><div>in un rifugio antiatomico</div>",
"HERO_SLIDE_2": "Progettato per sopravvivere",
"HERO_SLIDE_3_TITLE": "<div>Disponibile</div><div> ovunque</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Desktop",
"intro_slide_1_title": "<div>Backup privati</div><div>dei tuoi ricordi</div>",
"intro_slide_1": "Crittografia end-to-end",
"intro_slide_2_title": "<div>Salvati in modo sicuro</div><div>in un rifugio antiatomico</div>",
"intro_slide_2": "Progettato per sopravvivere",
"intro_slide_3_title": "<div>Disponibile</div><div> ovunque</div>",
"intro_slide_3": "Android, iOS, Web, Desktop",
"login": "Accedi",
"sign_up": "Registrati",
"NEW_USER": "Prima volta con Ente",
"EXISTING_USER": "Accedi",
"ENTER_NAME": "Inserisci il nome",
"enter_name": "Inserisci il nome",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Aggiungi un nome in modo che i tuoi amici sappiano chi ringraziare per queste fantastiche foto!",
"ENTER_EMAIL": "Inserisci l'indirizzo email",
"EMAIL_ERROR": "Inserisci un indirizzo email valido",
@@ -44,11 +44,11 @@
"create_albums": "Crea album",
"CREATE_COLLECTION": "Nuovo album",
"enter_album_name": "Nome album",
"CLOSE_OPTION": "Chiudi (Esc)",
"close_key": "Chiudi (Esc)",
"enter_file_name": "Nome del file",
"CLOSE": "Chiudi",
"NO": "No",
"NOTHING_HERE": "",
"close": "Chiudi",
"no": "No",
"nothing_here": "Non c'e ancora niente qui",
"upload": "Carica",
"import": "Importa",
"add_photos": "Aggiungi foto",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Password cambiata altrove",
"password_changed_elsewhere_message": "Effettua nuovamente il login su questo dispositivo per utilizzare la nuova password per autenticarti.",
"GO_BACK": "Torna indietro",
"RECOVERY_KEY": "Chiave di recupero",
"SAVE_LATER": "Fallo più tardi",
"SAVE": "Salva Chiave",
"recovery_key": "Chiave di recupero",
"do_this_later": "Fallo più tardi",
"save_key": "Salva Chiave",
"RECOVERY_KEY_DESCRIPTION": "Se dimentichi la tua password, l'unico modo per recuperare i tuoi dati è con questa chiave.",
"RECOVER_KEY_GENERATION_FAILED": "Impossibile generare il codice di recupero, riprova",
"KEY_NOT_STORED_DISCLAIMER": "Non memorizziamo questa chiave, quindi salvala in un luogo sicuro",
@@ -121,18 +121,17 @@
"RECOVER": "Recupera",
"NO_RECOVERY_KEY": "Nessuna chiave di recupero?",
"INCORRECT_RECOVERY_KEY": "Chiave di recupero errata",
"SORRY": "Siamo spiacenti",
"sorry": "Siamo spiacenti",
"NO_RECOVERY_KEY_MESSAGE": "A causa della natura del nostro protocollo di crittografia end-to-end, i tuoi dati non possono essere decifrati senza la tua password o chiave di ripristino",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Per favore invia un'email a <a>{{emailID}}</a> dal tuo indirizzo email registrato",
"CONTACT_SUPPORT": "Contatta il supporto",
"REQUEST_FEATURE": "Richiedi una funzionalità",
"SUPPORT": "Supporto",
"CONFIRM": "Conferma",
"contact_support": "Contatta il supporto",
"request_feature": "Richiedi una funzionalità",
"support": "Supporto",
"cancel": "Annulla",
"LOGOUT": "Disconnettiti",
"logout": "Disconnettiti",
"logout_message": "Sei sicuro di volerti disconnettere?",
"delete_account": "Elimina account",
"delete_account_manually_message": "<p>Per favore invia una email a <a>{{emailID}}</a> dal tuo indirizzo email registrato.</p><p>La tua richiesta verrà elaborata entro 72 ore.</p>",
"LOGOUT_MESSAGE": "Sei sicuro di volerti disconnettere?",
"CHANGE_EMAIL": "Cambia email",
"ok": "OK",
"success": "Operazione riuscita",
@@ -324,7 +323,7 @@
"hide_collection": "Nascondi album",
"unhide_collection": "Rimuovi album dai nascosti",
"MOVE": "Sposta",
"ADD": "Aggiungi",
"add": "Aggiungi",
"REMOVE": "Rimuovi",
"YES_REMOVE": "Sì, rimuovi",
"REMOVE_FROM_COLLECTION": "Rimuovi dall'album",
@@ -596,7 +595,7 @@
"COLORS": "Colori",
"FLIP": "Capovolgi",
"ROTATION": "Rotazione",
"RESET": "Reimposta",
"reset": "Reimposta",
"PHOTO_EDITOR": "Editor Foto",
"FASTER_UPLOAD": "Upload più veloci",
"FASTER_UPLOAD_DESCRIPTION": "Effettua l'upload attraverso i server vicini",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>당신의 추억을 위한</div><div>비공개 백업</div>",
"HERO_SLIDE_1": "종단간 암호화를 기본적으로 지원합니다",
"HERO_SLIDE_2_TITLE": "<div>낙진 대피소에</div><div>안전하게 보관됨</div>",
"HERO_SLIDE_2": "장기 보존을 위해 설계되었습니다",
"HERO_SLIDE_3_TITLE": "<div>모든 기기에서</div><div>사용 가능</div>",
"HERO_SLIDE_3": "안드로이드, iOS, 웹, 데스크탑",
"intro_slide_1_title": "<div>당신의 추억을 위한</div><div>비공개 백업</div>",
"intro_slide_1": "종단간 암호화를 기본적으로 지원합니다",
"intro_slide_2_title": "<div>낙진 대피소에</div><div>안전하게 보관됨</div>",
"intro_slide_2": "장기 보존을 위해 설계되었습니다",
"intro_slide_3_title": "<div>모든 기기에서</div><div>사용 가능</div>",
"intro_slide_3": "안드로이드, iOS, 웹, 데스크탑",
"login": "로그인",
"sign_up": "회원가입",
"NEW_USER": "Ente 의 새소식",
"EXISTING_USER": "기존 회원 로그인",
"ENTER_NAME": "이름 입력",
"enter_name": "이름 입력",
"PUBLIC_UPLOADER_NAME_MESSAGE": "친구들이 이 멋진 사진에 대해 고마워할 수 있도록 이름을 추가하세요!",
"ENTER_EMAIL": "이메일 주소를 입력하세요",
"EMAIL_ERROR": "올바른 이메일을 입력하세요",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "새 앨범",
"enter_album_name": "앨범 이름",
"CLOSE_OPTION": "닫기 (Esc)",
"close_key": "닫기 (Esc)",
"enter_file_name": "파일 이름",
"CLOSE": "닫기",
"NO": "아니오",
"NOTHING_HERE": "",
"close": "닫기",
"no": "아니오",
"nothing_here": "",
"upload": "업로드",
"import": "가져오기",
"add_photos": "사진 추가",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "뒤로 가기",
"RECOVERY_KEY": "키 복구하기",
"SAVE_LATER": "나중에 저장하기",
"SAVE": "키 저장하기",
"recovery_key": "키 복구하기",
"do_this_later": "나중에 저장하기",
"save_key": "키 저장하기",
"RECOVERY_KEY_DESCRIPTION": "암호 분실시, 오직 이 키를 이용해야만 데이터를 복구할 수 있습니다.",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "확인",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "취소",
"LOGOUT": "로그아웃",
"logout": "로그아웃",
"logout_message": "정말로 로그아웃 하시겠습니까?",
"delete_account": "계정 삭제",
"delete_account_manually_message": "<p>회원가입에 사용한 이메일을 통해 <a>{{emailID}}</a> (으)로 메일을 보내주세요.</p><p>귀하의 요청은 72시간 내로 처리됩니다.</p>",
"LOGOUT_MESSAGE": "정말로 로그아웃 하시겠습니까?",
"CHANGE_EMAIL": "이메일 주소 변경",
"ok": "확인",
"success": "성공",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Privé back-ups</div><div>voor uw herinneringen</div>",
"HERO_SLIDE_1": "Standaard end-to-end versleuteld",
"HERO_SLIDE_2_TITLE": "<div>Veilig opgeslagen</div><div>in een kernbunker</div>",
"HERO_SLIDE_2": "Ontworpen om levenslang mee te gaan",
"HERO_SLIDE_3_TITLE": "<div>Overal</div><div> beschikbaar</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Desktop",
"intro_slide_1_title": "<div>Privé back-ups</div><div>voor uw herinneringen</div>",
"intro_slide_1": "Standaard end-to-end versleuteld",
"intro_slide_2_title": "<div>Veilig opgeslagen</div><div>in een kernbunker</div>",
"intro_slide_2": "Ontworpen om levenslang mee te gaan",
"intro_slide_3_title": "<div>Overal</div><div> beschikbaar</div>",
"intro_slide_3": "Android, iOS, Web, Desktop",
"login": "Inloggen",
"sign_up": "Registreren",
"NEW_USER": "Nieuw bij Ente",
"EXISTING_USER": "Bestaande gebruiker",
"ENTER_NAME": "Naam invoeren",
"enter_name": "Naam invoeren",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Voeg een naam toe zodat je vrienden weten wie ze moeten bedanken voor deze geweldige foto's!",
"ENTER_EMAIL": "Vul e-mailadres in",
"EMAIL_ERROR": "Vul een geldig e-mailadres in",
@@ -44,11 +44,11 @@
"create_albums": "Albums aanmaken",
"CREATE_COLLECTION": "Nieuw album",
"enter_album_name": "Albumnaam",
"CLOSE_OPTION": "Sluiten (Esc)",
"close_key": "Sluiten (Esc)",
"enter_file_name": "Bestandsnaam",
"CLOSE": "Sluiten",
"NO": "Nee",
"NOTHING_HERE": "",
"close": "Sluiten",
"no": "Nee",
"nothing_here": "",
"upload": "Uploaden",
"import": "Importeren",
"add_photos": "Foto's toevoegen",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Wachtwoord elders gewijzigd",
"password_changed_elsewhere_message": "Log opnieuw in op dit apparaat om uw nieuwe wachtwoord te gebruiken om te verifiëren.",
"GO_BACK": "Ga terug",
"RECOVERY_KEY": "Herstelsleutel",
"SAVE_LATER": "Doe dit later",
"SAVE": "Sleutel opslaan",
"recovery_key": "Herstelsleutel",
"do_this_later": "Doe dit later",
"save_key": "Sleutel opslaan",
"RECOVERY_KEY_DESCRIPTION": "Als je je wachtwoord vergeet, kun je alleen met deze sleutel je gegevens herstellen.",
"RECOVER_KEY_GENERATION_FAILED": "Herstelcode kon niet worden gegenereerd, probeer het opnieuw",
"KEY_NOT_STORED_DISCLAIMER": "We slaan deze sleutel niet op, bewaar dit op een veilige plaats",
@@ -121,18 +121,17 @@
"RECOVER": "Herstellen",
"NO_RECOVERY_KEY": "Geen herstelsleutel?",
"INCORRECT_RECOVERY_KEY": "Onjuiste herstelsleutel",
"SORRY": "Sorry",
"sorry": "Sorry",
"NO_RECOVERY_KEY_MESSAGE": "Door de aard van ons end-to-end encryptieprotocol kunnen je gegevens niet worden ontsleuteld zonder je wachtwoord of herstelsleutel",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Stuur een e-mail naar <a>{{emailID}}</a> vanaf het door jou geregistreerde e-mailadres",
"CONTACT_SUPPORT": "Klantenservice",
"REQUEST_FEATURE": "Vraag nieuwe functie aan",
"SUPPORT": "Ondersteuning",
"CONFIRM": "Bevestigen",
"contact_support": "Klantenservice",
"request_feature": "Vraag nieuwe functie aan",
"support": "Ondersteuning",
"cancel": "Annuleren",
"LOGOUT": "Uitloggen",
"logout": "Uitloggen",
"logout_message": "Weet u zeker dat u wilt uitloggen?",
"delete_account": "Account verwijderen",
"delete_account_manually_message": "<p>Stuur een e-mail naar <a>{{emailID}}</a> vanaf uw geregistreerde e-mailadres.</p><p>Uw aanvraag wordt binnen 72 uur verwerkt.</p>",
"LOGOUT_MESSAGE": "Weet u zeker dat u wilt uitloggen?",
"CHANGE_EMAIL": "E-mail wijzigen",
"ok": "Oké",
"success": "Succes",
@@ -324,7 +323,7 @@
"hide_collection": "Verberg album",
"unhide_collection": "Album zichtbaar maken",
"MOVE": "Verplaatsen",
"ADD": "Toevoegen",
"add": "Toevoegen",
"REMOVE": "Verwijderen",
"YES_REMOVE": "Ja, verwijderen",
"REMOVE_FROM_COLLECTION": "Verwijderen uit album",
@@ -596,7 +595,7 @@
"COLORS": "Kleuren",
"FLIP": "Omdraaien",
"ROTATION": "Draaiing",
"RESET": "Herstellen",
"reset": "Herstellen",
"PHOTO_EDITOR": "Fotobewerker",
"FASTER_UPLOAD": "Snellere uploads",
"FASTER_UPLOAD_DESCRIPTION": "Uploaden door nabije servers",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Prywatne kopie zapasowe</div><div>dla Twoich wspomnień</div>",
"HERO_SLIDE_1": "Domyślnie zaszyfrowane metodą end-to-end",
"HERO_SLIDE_2_TITLE": "<div>Bezpiecznie przechowywane</div><div>w awaryjnym schronieniu</div>",
"HERO_SLIDE_2": "Zaprojektowane do przetrwania",
"HERO_SLIDE_3_TITLE": "<div>Dostępne</div><div> wszędzie</div>",
"HERO_SLIDE_3": "Android, iOS, Strona Internetowa, Aplikacja Komputerowa",
"intro_slide_1_title": "<div>Prywatne kopie zapasowe</div><div>dla Twoich wspomnień</div>",
"intro_slide_1": "Domyślnie zaszyfrowane metodą end-to-end",
"intro_slide_2_title": "<div>Bezpiecznie przechowywane</div><div>w awaryjnym schronieniu</div>",
"intro_slide_2": "Zaprojektowane do przetrwania",
"intro_slide_3_title": "<div>Dostępne</div><div> wszędzie</div>",
"intro_slide_3": "Android, iOS, Strona Internetowa, Aplikacja Komputerowa",
"login": "Zaloguj się",
"sign_up": "Zarejestruj się",
"NEW_USER": "Nowy/a do Ente",
"EXISTING_USER": "Istniejący użytkownik",
"ENTER_NAME": "Wprowadź nazwę",
"enter_name": "Wprowadź nazwę",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Dodaj imię, aby Twoi znajomi wiedzieli, kto będzie mógł podziękować za te wspaniałe zdjęcia!",
"ENTER_EMAIL": "Wprowadź adres e-mail",
"EMAIL_ERROR": "Wprowadź prawidłowy adres e-mail",
@@ -44,11 +44,11 @@
"create_albums": "Utwórz albumy",
"CREATE_COLLECTION": "Nowy album",
"enter_album_name": "Nazwa albumu",
"CLOSE_OPTION": "Zamknij (Esc)",
"close_key": "Zamknij (Esc)",
"enter_file_name": "Nazwa pliku",
"CLOSE": "Zamknij",
"NO": "Nie",
"NOTHING_HERE": "",
"close": "Zamknij",
"no": "Nie",
"nothing_here": "Nic tu jeszcze nie ma",
"upload": "Prześlij",
"import": "Importuj",
"add_photos": "Dodaj zdjęcia",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Hasło zostało zmienione gdzie indziej",
"password_changed_elsewhere_message": "Zaloguj się ponownie na tym urządzeniu, aby użyć nowego hasła, aby się uwierzytelnić.",
"GO_BACK": "Cofnij się",
"RECOVERY_KEY": "Klucz odzyskiwania",
"SAVE_LATER": "Zrób to później",
"SAVE": "Zapisz Klucz",
"recovery_key": "Klucz odzyskiwania",
"do_this_later": "Zrób to później",
"save_key": "Zapisz Klucz",
"RECOVERY_KEY_DESCRIPTION": "Jeśli zapomnisz swojego hasła, jedynym sposobem na odzyskanie Twoich danych jest ten klucz.",
"RECOVER_KEY_GENERATION_FAILED": "Nie można wygenerować kodu odzyskiwania, spróbuj ponownie",
"KEY_NOT_STORED_DISCLAIMER": "Nie przechowujemy tego klucza, prosimy zapisać to w bezpiecznym miejscu",
@@ -121,18 +121,17 @@
"RECOVER": "Odzyskaj",
"NO_RECOVERY_KEY": "Brak klucza odzyskiwania?",
"INCORRECT_RECOVERY_KEY": "Nieprawidłowy klucz odzyskiwania",
"SORRY": "Przepraszamy",
"sorry": "Przepraszamy",
"NO_RECOVERY_KEY_MESSAGE": "Ze względu na charakter naszego protokołu szyfrowania end-to-end, Twoje dane nie mogą być odszyfrowane bez hasła lub klucza odzyskiwania",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Wyślij wiadomość e-mail na <a>{{emailID}}</a> z zarejestrowanego adresu e-mail",
"CONTACT_SUPPORT": "Skontaktuj się z pomocą techniczną",
"REQUEST_FEATURE": "Zaproponuj Funkcję",
"SUPPORT": "Wsparcie Techniczne",
"CONFIRM": "Potwierdź",
"contact_support": "Skontaktuj się z pomocą techniczną",
"request_feature": "Zaproponuj Funkcję",
"support": "Wsparcie Techniczne",
"cancel": "Anuluj",
"LOGOUT": "Wyloguj się",
"logout": "Wyloguj się",
"logout_message": "Czy na pewno chcesz się wylogować?",
"delete_account": "Usuń konto",
"delete_account_manually_message": "<p>Prosimy wysłać wiadomość e-mail na <a>{{emailID}}</a> z Twojego zarejestrowanego adresu e-mail. </p><p>Twoja prośba zostanie przetworzona w ciągu 72 godzin.</p>",
"LOGOUT_MESSAGE": "Czy na pewno chcesz się wylogować?",
"CHANGE_EMAIL": "Zmień adres e-mail",
"ok": "OK",
"success": "Sukces",
@@ -324,7 +323,7 @@
"hide_collection": "Ukryj album",
"unhide_collection": "Odkryj album",
"MOVE": "Przenieś",
"ADD": "Dodaj",
"add": "Dodaj",
"REMOVE": "Usuń",
"YES_REMOVE": "Tak, usuń",
"REMOVE_FROM_COLLECTION": "Usuń z albumu",
@@ -596,7 +595,7 @@
"COLORS": "Kolory",
"FLIP": "Obróć",
"ROTATION": "Rotacja",
"RESET": "Zresetuj",
"reset": "Zresetuj",
"PHOTO_EDITOR": "Edytor Zdjęć",
"FASTER_UPLOAD": "Szybsze przesłania",
"FASTER_UPLOAD_DESCRIPTION": "Kieruj przesłania przez pobliskie serwery",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Backups privados</div><div>para as suas memórias</div>",
"HERO_SLIDE_1": "Criptografia de ponta a ponta por padrão",
"HERO_SLIDE_2_TITLE": "<div>Armazenado com segurança</div><div>em um abrigo avançado</div>",
"HERO_SLIDE_2": "Feito para ter longevidade",
"HERO_SLIDE_3_TITLE": "<div>Disponível</div><div> em qualquer lugar</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Desktop",
"intro_slide_1_title": "<div>Backups privados</div><div>para as suas memórias</div>",
"intro_slide_1": "Criptografia de ponta a ponta por padrão",
"intro_slide_2_title": "<div>Armazenado com segurança</div><div>em um abrigo avançado</div>",
"intro_slide_2": "Feito para ter longevidade",
"intro_slide_3_title": "<div>Disponível</div><div> em qualquer lugar</div>",
"intro_slide_3": "Android, iOS, Web, Desktop",
"login": "Entrar",
"sign_up": "Registrar",
"NEW_USER": "Novo no Ente",
"EXISTING_USER": "Usuário existente",
"ENTER_NAME": "Insira o nome",
"enter_name": "Insira o nome",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Adicione um nome para que os seus amigos saibam a quem agradecer por estas ótimas fotos!",
"ENTER_EMAIL": "Insira o endereço de e-mail",
"EMAIL_ERROR": "Inserir um endereço de e-mail válido",
@@ -44,11 +44,11 @@
"create_albums": "Criar álbuns",
"CREATE_COLLECTION": "Novo álbum",
"enter_album_name": "Nome do álbum",
"CLOSE_OPTION": "Fechar (Esc)",
"close_key": "Fechar (Esc)",
"enter_file_name": "Nome do arquivo",
"CLOSE": "Fechar",
"NO": "Não",
"NOTHING_HERE": "",
"close": "Fechar",
"no": "Não",
"nothing_here": "Nada aqui ainda",
"upload": "Enviar",
"import": "Importar",
"add_photos": "Adicionar fotos",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Senha alterada em outro lugar",
"password_changed_elsewhere_message": "Por favor, inicie sessão novamente neste dispositivo para usar sua nova senha para autenticar.",
"GO_BACK": "Voltar",
"RECOVERY_KEY": "Chave de recuperação",
"SAVE_LATER": "Fazer isso mais tarde",
"SAVE": "Salvar Chave",
"recovery_key": "Chave de recuperação",
"do_this_later": "Fazer isso mais tarde",
"save_key": "Salvar Chave",
"RECOVERY_KEY_DESCRIPTION": "Caso você esqueça sua senha, a única maneira de recuperar seus dados é com essa chave.",
"RECOVER_KEY_GENERATION_FAILED": "Não foi possível gerar o código de recuperação, tente novamente",
"KEY_NOT_STORED_DISCLAIMER": "Não armazenamos essa chave, por favor, salve essa chave de palavras em um lugar seguro",
@@ -121,18 +121,17 @@
"RECOVER": "Recuperar",
"NO_RECOVERY_KEY": "Não possui a chave de recuperação?",
"INCORRECT_RECOVERY_KEY": "Chave de recuperação incorreta",
"SORRY": "Desculpe",
"sorry": "Desculpe",
"NO_RECOVERY_KEY_MESSAGE": "Devido à natureza do nosso protocolo de criptografia de ponta a ponta, seus dados não podem ser descriptografados sem sua senha ou chave de recuperação",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Por favor, envie um e-mail para <a>{{emailID}}<a> a partir do seu endereço de e-mail registrado",
"CONTACT_SUPPORT": "Falar com o suporte",
"REQUEST_FEATURE": "Solicitar recurso",
"SUPPORT": "Suporte",
"CONFIRM": "Confirmar",
"contact_support": "Falar com o suporte",
"request_feature": "Solicitar recurso",
"support": "Suporte",
"cancel": "Cancelar",
"LOGOUT": "Encerrar sessão",
"logout": "Encerrar sessão",
"logout_message": "Você tem certeza que deseja encerrar a sessão?",
"delete_account": "Excluir conta",
"delete_account_manually_message": "<p>Por favor, envie um e-mail para <a>{{emailID}}</a> a partir do seu endereço de e-mail registrado.</p><p>Seu pedido será processado dentro de 72 horas.</p>",
"LOGOUT_MESSAGE": "Você tem certeza que deseja encerrar a sessão?",
"CHANGE_EMAIL": "Mudar e-mail",
"ok": "Aceitar",
"success": "Bem-sucedido",
@@ -324,7 +323,7 @@
"hide_collection": "Ocultar álbum",
"unhide_collection": "Reexibir álbum",
"MOVE": "Mover",
"ADD": "Adicionar",
"add": "Adicionar",
"REMOVE": "Remover",
"YES_REMOVE": "Sim, remover",
"REMOVE_FROM_COLLECTION": "Remover do álbum",
@@ -596,7 +595,7 @@
"COLORS": "Cores",
"FLIP": "Inverter",
"ROTATION": "Rotação",
"RESET": "Redefinir",
"reset": "Redefinir",
"PHOTO_EDITOR": "Editor de Fotos",
"FASTER_UPLOAD": "Envios mais rápidos",
"FASTER_UPLOAD_DESCRIPTION": "Rotas enviam em servidores próximos",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Backups privados</div><div>para as suas memórias</div>",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "<div>Disponível</div><div> em qualquer lugar</div>",
"HERO_SLIDE_3": "Android, iOS, Web, Desktop",
"intro_slide_1_title": "<div>Backups privados</div><div>para as suas memórias</div>",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "<div>Disponível</div><div> em qualquer lugar</div>",
"intro_slide_3": "Android, iOS, Web, Desktop",
"login": "Entrar",
"sign_up": "Registar",
"NEW_USER": "Novo no Ente",
"EXISTING_USER": "Utilizador existente",
"ENTER_NAME": "Insira o nome",
"enter_name": "Insira o nome",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Adicione um nome para que os seus amigos saibam a quem agradecer por estas ótimas fotos!",
"ENTER_EMAIL": "Insira o endereço de email",
"EMAIL_ERROR": "Inserir um endereço de email válido",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "Novo álbum",
"enter_album_name": "Nome do álbum",
"CLOSE_OPTION": "Fechar (Esc)",
"close_key": "Fechar (Esc)",
"enter_file_name": "Nome do ficheiro",
"CLOSE": "Fechar",
"NO": "Não",
"NOTHING_HERE": "",
"close": "Fechar",
"no": "Não",
"nothing_here": "",
"upload": "",
"import": "Importar",
"add_photos": "Adicionar fotos",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Приватные резервные копии</div><div>для ваших воспоминаний</div>",
"HERO_SLIDE_1": "Сквозное шифрование по умолчанию",
"HERO_SLIDE_2_TITLE": "<div>Надежно хранится</div><div>в убежище от радиоактивных осадков</div>",
"HERO_SLIDE_2": "Созданный для того, чтобы пережить",
"HERO_SLIDE_3_TITLE": "<div>Доступно</div><div> везде</div>",
"HERO_SLIDE_3": "Android, iOS, Веб, ПК",
"intro_slide_1_title": "<div>Приватные резервные копии</div><div>для ваших воспоминаний</div>",
"intro_slide_1": "Сквозное шифрование по умолчанию",
"intro_slide_2_title": "<div>Надежно хранится</div><div>в убежище от радиоактивных осадков</div>",
"intro_slide_2": "Созданный для того, чтобы пережить",
"intro_slide_3_title": "<div>Доступно</div><div> везде</div>",
"intro_slide_3": "Android, iOS, Веб, ПК",
"login": "Войти",
"sign_up": "Регистрация",
"NEW_USER": "Новенький в Ente",
"EXISTING_USER": "Существующий пользователь",
"ENTER_NAME": "Введите имя",
"enter_name": "Введите имя",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Добавьте имя, чтобы ваши друзья знали, кого благодарить за эти замечательные фотографии!",
"ENTER_EMAIL": "Введите адрес электронной почты",
"EMAIL_ERROR": "Введите действительный адрес электронной почты",
@@ -44,11 +44,11 @@
"create_albums": "Создать альбомы",
"CREATE_COLLECTION": "Новый альбом",
"enter_album_name": "Название альбома",
"CLOSE_OPTION": "Закрыть (Esc)",
"close_key": "Закрыть (Esc)",
"enter_file_name": "Имя файла",
"CLOSE": "Закрыть",
"NO": "Нет",
"NOTHING_HERE": "",
"close": "Закрыть",
"no": "Нет",
"nothing_here": "",
"upload": "Загрузить",
"import": "Импорт",
"add_photos": "Добавить фотографии",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Пароль изменен в другом месте",
"password_changed_elsewhere_message": "Пожалуйста, войдите снова на этом устройстве, чтобы использовать новый пароль для аутентификации.",
"GO_BACK": "Вернуться назад",
"RECOVERY_KEY": "Ключ восстановления",
"SAVE_LATER": "Сделать позже",
"SAVE": "Сохранить ключ",
"recovery_key": "Ключ восстановления",
"do_this_later": "Сделать позже",
"save_key": "Сохранить ключ",
"RECOVERY_KEY_DESCRIPTION": "Если вы забыли свой пароль, то восстановить данные можно только с помощью этого ключа.",
"RECOVER_KEY_GENERATION_FAILED": "Не удалось сгенерировать код восстановления, пожалуйста, повторите попытку",
"KEY_NOT_STORED_DISCLAIMER": "Мы не храним этот ключ, поэтому, пожалуйста, сохраните его в надежном месте",
@@ -121,18 +121,17 @@
"RECOVER": "Восстановить",
"NO_RECOVERY_KEY": "Нет ключа восстановления?",
"INCORRECT_RECOVERY_KEY": "Неправильный ключ восстановления",
"SORRY": "Извините",
"sorry": "Извините",
"NO_RECOVERY_KEY_MESSAGE": "Из-за природы нашего сквозного протокола шифрования ваши данные не могут быть расшифрованы без вашего пароля или ключа восстановления",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Пожалуйста, отправьте электронное письмо на адрес <a>{{emailID}}</a> с вашего зарегистрированного адреса электронной почты",
"CONTACT_SUPPORT": "Связаться с поддержкой",
"REQUEST_FEATURE": "Запросить функцию",
"SUPPORT": "Поддержка",
"CONFIRM": "Подтвердить",
"contact_support": "Связаться с поддержкой",
"request_feature": "Запросить функцию",
"support": "Поддержка",
"cancel": "Отменить",
"LOGOUT": "Выйти",
"logout": "Выйти",
"logout_message": "Вы уверены, что хотите выйти?",
"delete_account": "Удалить аккаунт",
"delete_account_manually_message": "<p>Пожалуйста, отправьте письмо по адресу <a>{{emailID}}</a> с вашего зарегистрированного адреса электронной почты.</p><p> Ваш запрос будет обработан в течение 72 часов</p>",
"LOGOUT_MESSAGE": "Вы уверены, что хотите выйти?",
"CHANGE_EMAIL": "Изменить адрес электронной почты",
"ok": "ОК",
"success": "Успешно",
@@ -324,7 +323,7 @@
"hide_collection": "Скрыть альбом",
"unhide_collection": "Показать альбом",
"MOVE": "Подвиньте",
"ADD": "Добавь",
"add": "Добавь",
"REMOVE": "Удалять",
"YES_REMOVE": "Да, удалить",
"REMOVE_FROM_COLLECTION": "Удалить из альбома",
@@ -596,7 +595,7 @@
"COLORS": "Цвета",
"FLIP": "Перевернуть",
"ROTATION": "Вращение",
"RESET": "Сбросить",
"reset": "Сбросить",
"PHOTO_EDITOR": "Редактор фото",
"FASTER_UPLOAD": "Более быстрая загрузка данных",
"FASTER_UPLOAD_DESCRIPTION": "Загрузка маршрута через близлежащие серверы",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>Privata säkerhetskopior</div><div>för dina minnen</div>",
"HERO_SLIDE_1": "Totalsträckskryptering som standard",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "Utformad för att överleva",
"HERO_SLIDE_3_TITLE": "<div>Tillgänglig</div><div> överallt</div>",
"HERO_SLIDE_3": "Android, iOS, webb, skrivbord",
"intro_slide_1_title": "<div>Privata säkerhetskopior</div><div>för dina minnen</div>",
"intro_slide_1": "Totalsträckskryptering som standard",
"intro_slide_2_title": "",
"intro_slide_2": "Utformad för att överleva",
"intro_slide_3_title": "<div>Tillgänglig</div><div> överallt</div>",
"intro_slide_3": "Android, iOS, webb, skrivbord",
"login": "Logga in",
"sign_up": "Registrera dig",
"NEW_USER": "Ny hos Ente",
"EXISTING_USER": "Befintlig användare",
"ENTER_NAME": "Ange namn",
"enter_name": "Ange namn",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Lägg till ett namn så att dina vänner vet vem de ska tacka för dessa fantastiska bilder!",
"ENTER_EMAIL": "Ange e-postadress",
"EMAIL_ERROR": "Ange en giltig e-postadress",
@@ -44,11 +44,11 @@
"create_albums": "Skapa album",
"CREATE_COLLECTION": "Nytt album",
"enter_album_name": "Albumnamn",
"CLOSE_OPTION": "Stäng (Esc)",
"close_key": "Stäng (Esc)",
"enter_file_name": "Filnamn",
"CLOSE": "Stäng",
"NO": "Nej",
"NOTHING_HERE": "",
"close": "Stäng",
"no": "Nej",
"nothing_here": "",
"upload": "Ladda upp",
"import": "Importera",
"add_photos": "Lägg till foton",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "Återställningsnyckel",
"SAVE_LATER": "",
"SAVE": "Spara nyckel",
"recovery_key": "Återställningsnyckel",
"do_this_later": "",
"save_key": "Spara nyckel",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "Återställ",
"NO_RECOVERY_KEY": "Ingen återställningsnyckel?",
"INCORRECT_RECOVERY_KEY": "Felaktig återställningsnyckel",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "Support",
"CONFIRM": "Bekräfta",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "Avbryt",
"LOGOUT": "Logga ut",
"logout": "Logga ut",
"logout_message": "Är du säker på att du vill logga ut?",
"delete_account": "Radera konto",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "Är du säker på att du vill logga ut?",
"CHANGE_EMAIL": "Ändra e-postadress",
"ok": "OK",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "Dölj album",
"unhide_collection": "",
"MOVE": "Flytta",
"ADD": "Lägg till",
"add": "Lägg till",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "Färger",
"FLIP": "",
"ROTATION": "",
"RESET": "Återställ",
"reset": "Återställ",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "",
"sign_up": "",
"NEW_USER": "",
"EXISTING_USER": "",
"ENTER_NAME": "",
"enter_name": "",
"PUBLIC_UPLOADER_NAME_MESSAGE": "",
"ENTER_EMAIL": "",
"EMAIL_ERROR": "",
@@ -44,11 +44,11 @@
"create_albums": "",
"CREATE_COLLECTION": "",
"enter_album_name": "",
"CLOSE_OPTION": "",
"close_key": "",
"enter_file_name": "",
"CLOSE": "",
"NO": "",
"NOTHING_HERE": "",
"close": "",
"no": "",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "",
"password_changed_elsewhere_message": "",
"GO_BACK": "",
"RECOVERY_KEY": "",
"SAVE_LATER": "",
"SAVE": "",
"recovery_key": "",
"do_this_later": "",
"save_key": "",
"RECOVERY_KEY_DESCRIPTION": "",
"RECOVER_KEY_GENERATION_FAILED": "",
"KEY_NOT_STORED_DISCLAIMER": "",
@@ -121,18 +121,17 @@
"RECOVER": "",
"NO_RECOVERY_KEY": "",
"INCORRECT_RECOVERY_KEY": "",
"SORRY": "",
"sorry": "",
"NO_RECOVERY_KEY_MESSAGE": "",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "",
"CONTACT_SUPPORT": "",
"REQUEST_FEATURE": "",
"SUPPORT": "",
"CONFIRM": "",
"contact_support": "",
"request_feature": "",
"support": "",
"cancel": "",
"LOGOUT": "",
"logout": "",
"logout_message": "",
"delete_account": "",
"delete_account_manually_message": "",
"LOGOUT_MESSAGE": "",
"CHANGE_EMAIL": "",
"ok": "",
"success": "",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "",
"HERO_SLIDE_1": "",
"HERO_SLIDE_2_TITLE": "",
"HERO_SLIDE_2": "",
"HERO_SLIDE_3_TITLE": "",
"HERO_SLIDE_3": "",
"intro_slide_1_title": "",
"intro_slide_1": "",
"intro_slide_2_title": "",
"intro_slide_2": "",
"intro_slide_3_title": "",
"intro_slide_3": "",
"login": "Giriş yap",
"sign_up": "Hesap aç",
"NEW_USER": "Yeni ente kullanıcısı",
"EXISTING_USER": "Mevcut kullanıcı",
"ENTER_NAME": "İsim gir",
"enter_name": "İsim gir",
"PUBLIC_UPLOADER_NAME_MESSAGE": "Arkadaşlarının bu harika fotoğraflar için kime teşekkür etmeleri gerektiğini bilmeleri için bir isim ekle!",
"ENTER_EMAIL": "E-posta adresini girin",
"EMAIL_ERROR": "Geçerli bir e-posta gir",
@@ -44,11 +44,11 @@
"create_albums": "Albüm oluştur",
"CREATE_COLLECTION": "Yeni albüm",
"enter_album_name": "Albüm adı",
"CLOSE_OPTION": "Kapat (Esc)",
"close_key": "Kapat (Esc)",
"enter_file_name": "Dosya adı",
"CLOSE": "Kapat",
"NO": "Hayır",
"NOTHING_HERE": "",
"close": "Kapat",
"no": "Hayır",
"nothing_here": "",
"upload": "",
"import": "",
"add_photos": "",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "Parola başka bir yerde değiştirildi",
"password_changed_elsewhere_message": "Lütfen yeni parolanı kullanarak kimlik doğrulaması yapmak için bu cihazda tekrar oturum aç.",
"GO_BACK": "Geri dön",
"RECOVERY_KEY": "Kurtarma anahtarı",
"SAVE_LATER": "Sonra yap",
"SAVE": "Anahtarı kaydet",
"recovery_key": "Kurtarma anahtarı",
"do_this_later": "Sonra yap",
"save_key": "Anahtarı kaydet",
"RECOVERY_KEY_DESCRIPTION": "Eğer parolanı unutursan, verilerini kurtarabileceğin tek yol bu anahtardır.",
"RECOVER_KEY_GENERATION_FAILED": "Kurtarma kodu oluşturulamadı, lütfen tekrar dene",
"KEY_NOT_STORED_DISCLAIMER": "Bu anahtarı saklamıyoruz, bu nedenle lütfen güvenli bir yerde sakla",
@@ -121,18 +121,17 @@
"RECOVER": "Kurtar",
"NO_RECOVERY_KEY": "Kurtarma anahtarı yok mu?",
"INCORRECT_RECOVERY_KEY": "Kurtarma anahtarı yanlış",
"SORRY": "Üzgünüz",
"sorry": "Üzgünüz",
"NO_RECOVERY_KEY_MESSAGE": "Uçtan uca şifreleme protokolümüzün doğası gereği, verilerin parolan veya kurtarma anahtarın olmadan çözülemez",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Lütfen kaydolduğun e-posta adresinden <a>{{emailID}}</a> adresine bir e-posta bırak",
"CONTACT_SUPPORT": "Destek ile iletişime geç",
"REQUEST_FEATURE": "Özellik İste",
"SUPPORT": "Destek",
"CONFIRM": "Onayla",
"contact_support": "Destek ile iletişime geç",
"request_feature": "Özellik İste",
"support": "Destek",
"cancel": "İptal",
"LOGOUT": ıkış yap",
"logout": ıkış yap",
"logout_message": ıkış yapmak istediğine emin misin?",
"delete_account": "Hesabı sil",
"delete_account_manually_message": "<p>Lütfen kaydolduğun e-posta adresinden <a>{{emailID}}</a> adresine bir e-posta gönder.</p><p>İsteğin 72 saat içinde işleme alınacaktır.</p>",
"LOGOUT_MESSAGE": ıkış yapmak istediğine emin misin?",
"CHANGE_EMAIL": "E-posta adresini değiştir",
"ok": "Tamam",
"success": "Başarılı",
@@ -324,7 +323,7 @@
"hide_collection": "",
"unhide_collection": "",
"MOVE": "",
"ADD": "",
"add": "",
"REMOVE": "",
"YES_REMOVE": "",
"REMOVE_FROM_COLLECTION": "",
@@ -596,7 +595,7 @@
"COLORS": "",
"FLIP": "",
"ROTATION": "",
"RESET": "",
"reset": "",
"PHOTO_EDITOR": "",
"FASTER_UPLOAD": "",
"FASTER_UPLOAD_DESCRIPTION": "",

View File

@@ -1,15 +1,15 @@
{
"HERO_SLIDE_1_TITLE": "<div>私人备份</div><div>为您的回忆</div>",
"HERO_SLIDE_1": "默认端到端加密",
"HERO_SLIDE_2_TITLE": "<div>安全地存放</div><div>在一个掩护所中</div>",
"HERO_SLIDE_2": "经久耐用",
"HERO_SLIDE_3_TITLE": "<div>可用于</div><div> 各处</div>",
"HERO_SLIDE_3": "安卓, iOS, 网页端, 桌面端",
"intro_slide_1_title": "<div>私人备份</div><div>为您的回忆</div>",
"intro_slide_1": "默认端到端加密",
"intro_slide_2_title": "<div>安全地存放</div><div>在一个掩护所中</div>",
"intro_slide_2": "经久耐用",
"intro_slide_3_title": "<div>可用于</div><div> 各处</div>",
"intro_slide_3": "安卓, iOS, 网页端, 桌面端",
"login": "登录",
"sign_up": "注册",
"NEW_USER": "初来 Ente",
"EXISTING_USER": "现有用户",
"ENTER_NAME": "输入名字",
"enter_name": "输入名字",
"PUBLIC_UPLOADER_NAME_MESSAGE": "请添加一个名字,以便您的朋友知晓该感谢谁拍摄了这些精美的照片!",
"ENTER_EMAIL": "请输入电子邮件地址",
"EMAIL_ERROR": "请输入有效的电子邮件",
@@ -44,11 +44,11 @@
"create_albums": "创建相册",
"CREATE_COLLECTION": "新建相册",
"enter_album_name": "相册名称",
"CLOSE_OPTION": "关闭 (或按Esc键)",
"close_key": "关闭 (或按Esc键)",
"enter_file_name": "文件名",
"CLOSE": "关闭",
"NO": "否",
"NOTHING_HERE": "",
"close": "关闭",
"no": "否",
"nothing_here": "这里什么也没有",
"upload": "上传",
"import": "导入",
"add_photos": "添加照片",
@@ -109,9 +109,9 @@
"password_changed_elsewhere": "密码已在别处更改",
"password_changed_elsewhere_message": "请在此设备上再次登录以使用您的新密码进行身份验证。",
"GO_BACK": "返回",
"RECOVERY_KEY": "恢复密钥",
"SAVE_LATER": "稍后再做",
"SAVE": "保存密钥",
"recovery_key": "恢复密钥",
"do_this_later": "稍后再做",
"save_key": "保存密钥",
"RECOVERY_KEY_DESCRIPTION": "如果您忘记了密码,恢复数据的唯一方法就是使用此密钥。",
"RECOVER_KEY_GENERATION_FAILED": "无法生成恢复代码,请重试",
"KEY_NOT_STORED_DISCLAIMER": "我们不存储此密钥,因此请将其保存在安全的地方",
@@ -121,18 +121,17 @@
"RECOVER": "恢复",
"NO_RECOVERY_KEY": "没有恢复密钥?",
"INCORRECT_RECOVERY_KEY": "不正确的恢复密钥",
"SORRY": "抱歉",
"sorry": "抱歉",
"NO_RECOVERY_KEY_MESSAGE": "由于我们端到端加密协议的性质,如果没有您的密码或恢复密钥,您的数据将无法解密",
"NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "请用您注册Ente账户的电子邮箱发一封邮件给 <a>{{emailID}}</a>",
"CONTACT_SUPPORT": "联系支持",
"REQUEST_FEATURE": "功能建议",
"SUPPORT": "支持",
"CONFIRM": "确认",
"contact_support": "联系支持",
"request_feature": "功能建议",
"support": "支持",
"cancel": "取消",
"LOGOUT": "退出登录",
"logout": "退出登录",
"logout_message": "你确定要退出登录吗?",
"delete_account": "删除账户",
"delete_account_manually_message": "<p>请从您注册的电子邮件地址发送一封电子邮件到 <a>{{emailID}}</a></p><p>。您的请求将在72小时内处理。</p>",
"LOGOUT_MESSAGE": "你确定要退出登录吗?",
"CHANGE_EMAIL": "更换邮箱",
"ok": "确定",
"success": "成功",
@@ -324,7 +323,7 @@
"hide_collection": "隐藏相册",
"unhide_collection": "取消隐藏相册",
"MOVE": "移动",
"ADD": "添加",
"add": "添加",
"REMOVE": "移除",
"YES_REMOVE": "是,移除",
"REMOVE_FROM_COLLECTION": "从相册中移除",
@@ -596,7 +595,7 @@
"COLORS": "颜色",
"FLIP": "上下翻转",
"ROTATION": "回转",
"RESET": "重设",
"reset": "重设",
"PHOTO_EDITOR": "照片编辑器",
"FASTER_UPLOAD": "更快上传",
"FASTER_UPLOAD_DESCRIPTION": "通过附近的服务器路由上传",

View File

@@ -52,6 +52,10 @@ import {
export type GalleryBarMode = "albums" | "hidden-albums" | "people";
export interface GalleryBarImplProps {
/**
* When `true`, the bar shows a button to switch to the people section.
*/
showPeopleSectionButton: boolean;
/**
* What are we displaying currently.
*/
@@ -94,11 +98,9 @@ export interface GalleryBarImplProps {
*/
people: Person[];
/**
* The ID of the currently selected person.
*
* Required if mode is "people".
* The currently selected person, if any.
*/
activePersonID: string | undefined;
activePerson: Person | undefined;
/**
* Called when the selection should be moved to a new person in the bar, or
* reset to the default state (when {@link person} is `undefined`).
@@ -107,6 +109,7 @@ export interface GalleryBarImplProps {
}
export const GalleryBarImpl: React.FC<GalleryBarImplProps> = ({
showPeopleSectionButton,
mode,
onChangeMode,
collectionSummaries,
@@ -116,7 +119,7 @@ export const GalleryBarImpl: React.FC<GalleryBarImplProps> = ({
collectionsSortBy,
onChangeCollectionsSortBy,
people,
activePersonID,
activePerson,
onSelectPerson,
}) => {
const isMobile = useIsMobileWidth();
@@ -194,11 +197,11 @@ export const GalleryBarImpl: React.FC<GalleryBarImplProps> = ({
);
break;
case "people":
i = people.findIndex(({ id }) => id == activePersonID);
i = people.findIndex(({ id }) => id == activePerson?.id);
break;
}
if (i != -1) listRef.current.scrollToItem(i, "smart");
}, [mode, collectionSummaries, activeCollectionID, people, activePersonID]);
}, [mode, collectionSummaries, activeCollectionID, people, activePerson]);
const itemData = useMemo<ItemData>(
() =>
@@ -210,12 +213,9 @@ export const GalleryBarImpl: React.FC<GalleryBarImplProps> = ({
onSelectCollectionID,
}
: {
type: "people",
type: "people" as const,
people,
activePerson: ensure(
people.find((p) => p.id == activePersonID) ??
people[0],
),
activePerson,
onSelectPerson,
},
[
@@ -224,12 +224,12 @@ export const GalleryBarImpl: React.FC<GalleryBarImplProps> = ({
activeCollectionID,
onSelectCollectionID,
people,
activePersonID,
activePerson,
onSelectPerson,
],
);
const controls1 = isMobile && (
const controls1 = isMobile && mode != "people" && (
<Box display="flex" alignItems={"center"} gap={1}>
<CollectionsSortOptions
activeSortBy={collectionsSortBy}
@@ -242,7 +242,7 @@ export const GalleryBarImpl: React.FC<GalleryBarImplProps> = ({
</Box>
);
const controls2 = !isMobile && (
const controls2 = !isMobile && mode != "people" && (
<Box display="flex" alignItems={"center"} gap={1} height={"64px"}>
<CollectionsSortOptions
activeSortBy={collectionsSortBy}
@@ -255,9 +255,14 @@ export const GalleryBarImpl: React.FC<GalleryBarImplProps> = ({
);
return (
<BarWrapper>
// Hide the bottom border if we're showing the empty state for people.
<BarWrapper
sx={people.length ? {} : { borderBlockEndColor: "transparent" }}
>
<Row1>
<ModeIndicator {...{ mode, onChangeMode }} />
<ModeIndicator
{...{ showPeopleSectionButton, mode, onChangeMode }}
/>
{controls1}
</Row1>
<Row2>
@@ -314,33 +319,46 @@ export const Row2 = styled(Box)`
`;
const ModeIndicator: React.FC<
Pick<GalleryBarImplProps, "mode" | "onChangeMode">
> = ({ mode, onChangeMode }) => {
Pick<
GalleryBarImplProps,
"showPeopleSectionButton" | "mode" | "onChangeMode"
>
> = ({ showPeopleSectionButton, mode, onChangeMode }) => {
// Mode switcher is not shown in the hidden albums section.
if (mode == "hidden-albums") {
return <Typography>{t("hidden_albums")}</Typography>;
}
// Show the static mode indicator with only the "Albums" title unless we
// come here with the people mode already set. This is because we don't
// currently have an empty state for the People mode when ML is not enabled.
if (mode == "albums") {
// Show the static mode indicator with only the "Albums" title if we have
// not been asked to show the people button (there are no other sections to
// switch to in such a case).
if (!showPeopleSectionButton) {
return <Typography>{t("albums")}</Typography>;
}
return (
<Stack direction="row" sx={{ gap: "10px" }}>
<AlbumModeButton onClick={() => onChangeMode("albums")}>
<ModeButton
active={mode == "albums"}
onClick={() => onChangeMode("albums")}
>
<Typography>{t("albums")}</Typography>
</AlbumModeButton>
<Typography>{t("people")}</Typography>
</ModeButton>
<ModeButton
active={mode == "people"}
onClick={() => onChangeMode("people")}
>
<Typography>{t("people")}</Typography>
</ModeButton>
</Stack>
);
};
const AlbumModeButton = styled(UnstyledButton)(
({ theme }) => `
p { color: ${theme.colors.text.muted} }
const ModeButton = styled(UnstyledButton, {
shouldForwardProp: (propName) => propName != "active",
})<{ active: boolean }>(
({ active, theme }) => `
p { color: ${active ? theme.colors.text.base : theme.colors.text.muted} }
p:hover { color: ${theme.colors.text.base} }
`,
);
@@ -407,7 +425,7 @@ type ItemData =
| {
type: "people";
people: Person[];
activePerson: Person;
activePerson: Person | undefined;
onSelectPerson: (person: Person) => void;
};
@@ -573,7 +591,7 @@ const ActiveIndicator = styled("div")`
interface PersonCardProps {
person: Person;
activePerson: Person;
activePerson: Person | undefined;
onSelectPerson: (person: Person) => void;
}
@@ -586,10 +604,11 @@ const PersonCard: React.FC<PersonCardProps> = ({
<ItemCard
TileComponent={BarItemTile}
coverFile={person.displayFaceFile}
coverFaceID={person.displayFaceID}
onClick={() => onSelectPerson(person)}
>
{person.name && <CardText text={person.name} />}
</ItemCard>
{activePerson.id === person.id && <ActiveIndicator />}
{activePerson?.id === person.id && <ActiveIndicator />}
</Box>
);

View File

@@ -31,6 +31,27 @@ import { NameInputDialog } from "../NameInputDialog";
import type { GalleryBarImplProps } from "./BarImpl";
import { GalleryItemsHeaderAdapter, GalleryItemsSummary } from "./ListHeader";
/**
* Derived UI state backing the gallery when it is in "people" mode.
*
* This may be different from the actual underlying state since there might be
* unsynced data (hidden or deleted that have not yet been synced with remote)
* that should be taken into account for the UI state.
*/
export interface GalleryPeopleState {
/**
* The currently selected person, if any.
*
* Whenever this is present, it is guaranteed to be one of the items from
* within {@link people}.
*/
activePerson: Person | undefined;
/**
* The list of people to show.
*/
people: Person[];
}
type PeopleHeaderProps = Pick<GalleryBarImplProps, "onSelectPerson"> & {
person: Person;
appContext: NewAppContextPhotos;
@@ -107,7 +128,7 @@ const CGroupPersonOptions: React.FC<CGroupPersonOptionsProps> = ({
),
close: { text: t("cancel") },
proceed: {
text: t("RESET"),
text: t("reset"),
action: doDeletePerson,
},
buttonDirection: "row",
@@ -151,8 +172,8 @@ const CGroupPersonOptions: React.FC<CGroupPersonOptionsProps> = ({
<NameInputDialog
open={openAddNameInput}
onClose={() => setOpenAddNameInput(false)}
title={pt("Rename person")}
placeholder={t("ENTER_NAME") /* TODO-Cluster */}
title={pt("Rename person") /* TODO-Cluster pt()'s */}
placeholder={t("enter_name")}
initialValue={cgroup.data.name ?? ""}
submitButtonTitle={t("rename")}
onSubmit={renamePersonUsingName}
@@ -210,10 +231,10 @@ const ClusterPersonOptions: React.FC<ClusterPersonOptionsProps> = ({
<NameInputDialog
open={openNameInput}
onClose={() => setOpenNameInput(false)}
title={pt("Add person")}
placeholder={t("ENTER_NAME") /* TODO-Cluster */}
title={pt("Add person") /* TODO-Cluster */}
placeholder={t("enter_name")}
initialValue={""}
submitButtonTitle={t("ADD")}
submitButtonTitle={t("add")}
onSubmit={addPersonWithName}
/>
</>

View File

@@ -7,7 +7,9 @@
* there.
*/
import { pt } from "@/base/i18n";
import type { SearchOption } from "@/new/photos/services/search/types";
import { VerticallyCentered } from "@ente/shared/components/Container";
import { Typography } from "@mui/material";
import { t } from "i18next";
import React from "react";
@@ -40,3 +42,17 @@ export const SearchResultsHeader: React.FC<SearchResultsHeaderProps> = ({
/>
</GalleryItemsHeaderAdapter>
);
export const PeopleEmptyState: React.FC = () => (
<VerticallyCentered>
<Typography
color="text.muted"
sx={{
// Approximately compensate for the hidden section bar
paddingBlockEnd: "86px",
}}
>
{pt("People will appear here once indexing completes")}
</Typography>
</VerticallyCentered>
);

View File

@@ -6,6 +6,7 @@ import downloadManager from "@/new/photos/services/download";
import { type EnteFile } from "@/new/photos/types/file";
import { styled } from "@mui/material";
import React, { useEffect, useState } from "react";
import { faceCrop } from "../services/ml";
interface ItemCardProps {
/**
@@ -16,11 +17,17 @@ interface ItemCardProps {
* Optional file whose thumbnail (if any) should be should be shown.
*/
coverFile?: EnteFile | undefined;
/**
* Optional ID of a specific face within {@link coverFile} to show.
*
* Precondition: {@link faceID} must be an ID of a face that belongs to the
* given {@link coverFile}.
*/
coverFaceID?: string | undefined;
/**
* Optional boolean indicating if the user is currently scrolling.
*
* This is used as a hint by the cover file downloader to prioritize
* downloads.
* This is used as a hint by the file downloader to prioritize downloads.
*/
isScrolling?: boolean;
/**
@@ -28,25 +35,50 @@ interface ItemCardProps {
*/
onClick?: () => void;
}
/**
* A generic card that can be be used to represent collections, files, people -
* anything that (usually) has an associated "cover photo".
*
* Usually, we provide it a {@link coverFile} prop to set the file whose
* thumbnail should be shown in the card. However, an additional
* {@link coverFaceID} prop can be used to show the face crop for that specific
* face within the cover file.
*
* Note that while the common use case is to use this with a cover photo (and an
* additional cover faceID), both of these are optional and the item card can
* also be used as a static component without an associated cover image by
* covering it with an opaque overlay.
*/
export const ItemCard: React.FC<React.PropsWithChildren<ItemCardProps>> = ({
TileComponent,
coverFile,
coverFaceID,
isScrolling,
onClick,
children,
}) => {
const [coverImageURL, setCoverImageURL] = useState("");
const [coverImageURL, setCoverImageURL] = useState<string | undefined>();
useEffect(() => {
if (!coverFile) return;
void downloadManager
.getThumbnailForPreview(coverFile, isScrolling)
.then((url) => url && setCoverImageURL(url));
}, [coverFile, isScrolling]);
let didCancel = false;
if (coverFaceID) {
void faceCrop(coverFaceID, coverFile).then(
(url) => !didCancel && setCoverImageURL(url),
);
} else {
void downloadManager
.getThumbnailForPreview(coverFile, isScrolling)
.then((url) => !didCancel && setCoverImageURL(url));
}
return () => {
didCancel = true;
};
}, [coverFile, coverFaceID, isScrolling]);
return (
<TileComponent {...{ onClick }}>

View File

@@ -1,5 +1,6 @@
import { useIsMobileWidth } from "@/base/hooks";
import { faceCrop, unidentifiedFaceIDs } from "@/new/photos/services/ml";
import { pt } from "@/base/i18n";
import { faceCrop, type AnnotatedFaceID } from "@/new/photos/services/ml";
import type { Person } from "@/new/photos/services/ml/people";
import type { EnteFile } from "@/new/photos/types/file";
import { Skeleton, Typography, styled } from "@mui/material";
@@ -25,7 +26,7 @@ export const SearchPeopleList: React.FC<SearchPeopleListProps> = ({
sx={{ justifyContent: people.length > 3 ? "center" : "start" }}
>
{people.slice(0, isMobileWidth ? 6 : 7).map((person) => (
<SearchPeopleButton
<SearchPersonButton
key={person.id}
onClick={() => onSelectPerson(person)}
>
@@ -34,7 +35,7 @@ export const SearchPeopleList: React.FC<SearchPeopleListProps> = ({
enteFile={person.displayFaceFile}
placeholderDimension={87}
/>
</SearchPeopleButton>
</SearchPersonButton>
))}
</SearchPeopleContainer>
);
@@ -49,7 +50,7 @@ const SearchPeopleContainer = styled("div")`
margin-block-end: 15px;
`;
const SearchPeopleButton = styled(UnstyledButton)(
const SearchPersonButton = styled(UnstyledButton)(
({ theme }) => `
width: 87px;
height: 87px;
@@ -66,88 +67,132 @@ const SearchPeopleButton = styled(UnstyledButton)(
`,
);
const FaceChipContainer = styled("div")`
export interface AnnotatedFacePeopleListProps {
/**
* The {@link EnteFile} whose information we are showing.
*/
enteFile: EnteFile;
/**
* The list of faces in the file that are associated with a person.
*/
annotatedFaceIDs: AnnotatedFaceID[];
/**
* Called when the user selects a face in the list.
*/
onSelectFace: (annotatedFaceID: AnnotatedFaceID) => void;
}
/**
* Show the list of faces in the given file that are associated with a specific
* person.
*/
export const AnnotatedFacePeopleList: React.FC<
AnnotatedFacePeopleListProps
> = ({ enteFile, annotatedFaceIDs, onSelectFace }) => {
if (annotatedFaceIDs.length == 0) return <></>;
return (
<>
<Typography variant="large" p={1}>
{t("people")}
</Typography>
<FileFaceList>
{annotatedFaceIDs.map((annotatedFaceID) => (
<AnnotatedFaceButton
key={annotatedFaceID.faceID}
onClick={() => onSelectFace(annotatedFaceID)}
>
<FaceCropImageView
faceID={annotatedFaceID.faceID}
enteFile={enteFile}
placeholderDimension={112}
/>
</AnnotatedFaceButton>
))}
</FileFaceList>
</>
);
};
const FileFaceList = styled("div")`
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
margin-top: 5px;
margin-bottom: 5px;
overflow: auto;
gap: 5px;
margin: 5px;
`;
const FaceChip = styled("div")<{ clickable?: boolean }>`
const AnnotatedFaceButton = styled(UnstyledButton)(
({ theme }) => `
width: 112px;
height: 112px;
margin: 5px;
border-radius: 50%;
overflow: hidden;
position: relative;
cursor: ${({ clickable }) => (clickable ? "pointer" : "normal")};
& > img {
width: 100%;
height: 100%;
}
`;
:hover {
outline: 1px solid ${theme.colors.stroke.faint};
outline-offset: 2px;
}
`,
);
export interface PhotoPeopleListProps {
file: EnteFile;
onSelect?: (person: Person, index: number) => void;
}
export function PhotoPeopleList() {
return <></>;
}
interface UnidentifiedFacesProps {
export interface UnclusteredFaceListProps {
/**
* The {@link EnteFile} whose information we are showing.
*/
enteFile: EnteFile;
/**
* The list of faces in the file that are not associated with a person.
*/
faceIDs: string[];
}
/**
* Show the list of faces in the given file that are not linked to a specific
* person ("face cluster").
* Show the list of faces in the given file that are not associated with a
* specific person.
*/
export const UnidentifiedFaces: React.FC<UnidentifiedFacesProps> = ({
export const UnclusteredFaceList: React.FC<UnclusteredFaceListProps> = ({
enteFile,
faceIDs,
}) => {
const [faceIDs, setFaceIDs] = useState<string[]>([]);
useEffect(() => {
let didCancel = false;
const go = async () => {
const faceIDs = await unidentifiedFaceIDs(enteFile);
!didCancel && setFaceIDs(faceIDs);
};
void go();
return () => {
didCancel = true;
};
}, [enteFile]);
if (faceIDs.length == 0) return <></>;
return (
<>
<Typography variant="large" p={1}>
{t("UNIDENTIFIED_FACES")}
{pt("Other faces")}
{/*t("UNIDENTIFIED_FACES") TODO-Cluster */}
</Typography>
<FaceChipContainer>
<FileFaceList>
{faceIDs.map((faceID) => (
<FaceChip key={faceID}>
<UnclusteredFace key={faceID}>
<FaceCropImageView
placeholderDimension={112}
{...{ enteFile, faceID }}
/>
</FaceChip>
</UnclusteredFace>
))}
</FaceChipContainer>
</FileFaceList>
</>
);
};
const UnclusteredFace = styled("div")`
width: 112px;
height: 112px;
margin: 5px;
border-radius: 50%;
overflow: hidden;
& > img {
width: 100%;
height: 100%;
}
`;
interface FaceCropImageViewProps {
/** The ID of the face to display. */
faceID: string;
@@ -171,25 +216,22 @@ const FaceCropImageView: React.FC<FaceCropImageViewProps> = ({
enteFile,
placeholderDimension,
}) => {
const [objectURL, setObjectURL] = useState<string | undefined>();
const [url, setURL] = useState<string | undefined>();
useEffect(() => {
let didCancel = false;
let thisObjectURL: string | undefined;
void faceCrop(faceID, enteFile).then((blob) => {
if (blob && !didCancel)
setObjectURL((thisObjectURL = URL.createObjectURL(blob)));
});
void faceCrop(faceID, enteFile).then(
(url) => !didCancel && setURL(url),
);
return () => {
didCancel = true;
if (thisObjectURL) URL.revokeObjectURL(thisObjectURL);
};
}, [faceID, enteFile]);
return objectURL ? (
<img style={{ objectFit: "cover" }} src={objectURL} />
return url ? (
<img style={{ objectFit: "cover" }} src={url} />
) : (
<Skeleton
variant="circular"

View File

@@ -144,6 +144,21 @@ class DownloadManagerImpl {
return thumb;
}
/**
* Resolves with an URL that points to the file's thumbnail.
*
* The thumbnail will be downloaded (unless {@link localOnly} is true) and
* cached.
*
* The optional {@link localOnly} parameter can be set to indicate that this
* is being called as part of a scroll, so the downloader should not attempt
* to download the file but should instead fulfill the request from the
* cache. This avoids an unbounded flurry of requests on scroll, only
* downloading when the position has quiescized.
*
* The returned URL is actually an object URL, but it should not be revoked
* since the download manager caches it for future use.
*/
async getThumbnailForPreview(
file: EnteFile,
localOnly = false,

View File

@@ -1,3 +1,4 @@
import { assertionFailed } from "@/base/assert";
import { newNonSecureID } from "@/base/id-worker";
import log from "@/base/log";
import { ensure } from "@/utils/ensure";
@@ -204,18 +205,24 @@ const sortFacesNewestOnesFirst = (
const fileForFaceID = new Map(
faces.map(({ faceID }) => [
faceID,
ensure(localFileByID.get(ensure(fileIDFromFaceID(faceID)))),
localFileByID.get(ensure(fileIDFromFaceID(faceID))),
]),
);
const fileForFace = ({ faceID }: { faceID: string }) =>
ensure(fileForFaceID.get(faceID));
// In unexpected scenarios, we might run clustering without having the
// corresponding EnteFile available locally. This shouldn't happen, so log
// an warning, but meanwhile let the clustering proceed by assigning such
// files an arbitrary creationTime.
const sortTimeForFace = ({ faceID }: { faceID: string }) => {
const file = fileForFaceID.get(faceID);
if (!file) {
assertionFailed(`Did not find a local file for faceID ${faceID}`);
return 0;
}
return file.metadata.creationTime;
};
return faces.sort(
(a, b) =>
fileForFace(b).metadata.creationTime -
fileForFace(a).metadata.creationTime,
);
return faces.sort((a, b) => sortTimeForFace(b) - sortTimeForFace(a));
};
/**
@@ -294,10 +301,6 @@ const clusterBatchLinear = async (
if (csim > nnCosineSimilarity && csim >= threshold) {
nnIndex = j;
nnCosineSimilarity = csim;
// If we've find something above our early exit threshold, stop
// looking for a better match (A way to speed up clustering).
if (csim >= 0.9) break;
}
}
@@ -340,22 +343,22 @@ export const reconcileClusters = async (
const clusterByID = new Map(clusters.map((c) => [c.id, c]));
// Get the existing remote cluster groups.
const cgroupEntities = await savedCGroups();
const cgroups = await savedCGroups();
// Find the cgroups that have changed since we started.
const changedCGroupEntities = cgroupEntities
.map((cgroupEntity) => {
for (const oldCluster of cgroupEntity.data.assigned) {
const changedCGroups = cgroups
.map((cgroup) => {
for (const oldCluster of cgroup.data.assigned) {
// The clustering algorithm does not remove any existing faces, it
// can only add new ones to the cluster. So we can use the count as
// an indication if something changed.
const newCluster = ensure(clusterByID.get(oldCluster.id));
if (oldCluster.faces.length != newCluster.faces.length) {
return {
...cgroupEntity,
...cgroup,
data: {
...cgroupEntity.data,
assigned: cgroupEntity.data.assigned.map(({ id }) =>
...cgroup.data,
assigned: cgroup.data.assigned.map(({ id }) =>
ensure(clusterByID.get(id)),
),
},
@@ -367,19 +370,15 @@ export const reconcileClusters = async (
.filter((g) => !!g);
// Update remote if needed.
if (changedCGroupEntities.length) {
await updateOrCreateUserEntities(
"cgroup",
changedCGroupEntities,
masterKey,
);
log.info(`Updated ${changedCGroupEntities.length} remote cgroups`);
if (changedCGroups.length) {
await updateOrCreateUserEntities("cgroup", changedCGroups, masterKey);
log.info(`Updated ${changedCGroups.length} remote cgroups`);
}
// Find which clusters are part of remote cgroups.
const isRemoteClusterID = new Set<string>();
for (const cgroupEntity of cgroupEntities) {
for (const cluster of cgroupEntity.data.assigned)
for (const cgroup of cgroups) {
for (const cluster of cgroup.data.assigned)
isRemoteClusterID.add(cluster.id);
}

View File

@@ -5,7 +5,6 @@
import { isDesktop } from "@/base/app";
import { blobCache } from "@/base/blob-cache";
import { ensureElectron } from "@/base/electron";
import { isDevBuild } from "@/base/env";
import log from "@/base/log";
import { masterKeyFromSession } from "@/base/session-store";
import type { Electron } from "@/base/types/ipc";
@@ -15,7 +14,6 @@ import type { EnteFile } from "@/new/photos/types/file";
import { ensure } from "@/utils/ensure";
import { throttled } from "@/utils/promise";
import { proxy, transfer } from "comlink";
import { isInternalUser } from "../feature-flags";
import { getRemoteFlag, updateRemoteFlag } from "../remote-store";
import { setSearchPeople } from "../search";
import type { UploadItem } from "../upload/types";
@@ -88,6 +86,9 @@ class MLState {
/**
* Snapshot of the {@link Person}s returned by the {@link peopleSnapshot}
* function.
*
* It will be `undefined` only if ML is disabled. Otherwise, it will be an
* empty array even if the snapshot is pending its first sync.
*/
peopleSnapshot: Person[] | undefined;
@@ -96,6 +97,13 @@ class MLState {
* whose faces we are regenerating.
*/
inFlightFaceCropRegens = new Map<number, Promise<void>>();
/**
* Cached object URLs to face crops that we have previously vended out.
*
* The cache is only cleared on logout.
*/
faceCropObjectURLCache = new Map<string, string>();
}
/** State shared by the functions in this module. See {@link MLState}. */
@@ -107,7 +115,7 @@ const worker = () =>
const createComlinkWorker = async () => {
const electron = ensureElectron();
const delegate = { workerDidUpdateStatus };
const delegate = { workerDidUpdateStatus, workerDidUnawaitedIndex };
// Obtain a message port from the Electron layer.
const messagePort = await createMLWorker(electron);
@@ -180,6 +188,7 @@ export const isMLSupported = isDesktop;
*/
export const initML = () => {
_state.isMLEnabled = isMLEnabledLocal();
resetPeopleSnapshot();
};
export const logoutML = async () => {
@@ -188,6 +197,9 @@ export const logoutML = async () => {
// execution contexts], it gets called first in the logout sequence, and
// then this function (`logoutML`) gets called at a later point in time.
[..._state.faceCropObjectURLCache.values()].forEach((url) =>
URL.revokeObjectURL(url),
);
_state = new MLState();
await clearMLDB();
};
@@ -213,6 +225,7 @@ export const enableML = async () => {
setIsMLEnabledLocal(true);
_state.isMLEnabled = true;
setInterimScheduledStatus();
resetPeopleSnapshot();
// Trigger updates, but don't wait for them to finish.
void updateMLStatusSnapshot().then(mlSync);
};
@@ -230,6 +243,7 @@ export const disableML = async () => {
_state.isSyncing = false;
await terminateMLWorker();
triggerStatusUpdate();
resetPeopleSnapshot();
};
/**
@@ -313,30 +327,32 @@ export const mlSync = async () => {
// Dependency order for the sync
//
// files -> faces -> cgroups -> clusters
// files -> faces -> cgroups -> clusters -> people
//
const w = await worker();
// Fetch indexes, or index locally if needed.
await w.index();
await (await worker()).index();
// TODO-Cluster
if (await wipClusterEnable()) {
const masterKey = await masterKeyFromSession();
// Fetch existing cgroups from remote.
await pullUserEntities("cgroup", masterKey);
// Generate or update local clusters.
await w.clusterFaces(masterKey);
}
await updatePeople();
await updateClustersAndPeople();
_state.isSyncing = false;
};
const workerDidUnawaitedIndex = () => void updateClustersAndPeople();
const updateClustersAndPeople = async () => {
const masterKey = await masterKeyFromSession();
// Fetch existing cgroups from remote.
await pullUserEntities("cgroup", masterKey);
// Generate or update local clusters.
await (await worker()).clusterFaces(masterKey);
// Update the people shown in the UI.
await updatePeople();
};
/**
* Run indexing on a file which was uploaded from this client.
*
@@ -361,14 +377,6 @@ export const indexNewUpload = (enteFile: EnteFile, uploadItem: UploadItem) => {
void worker().then((w) => w.onUpload(enteFile, uploadItem));
};
/**
* WIP! Don't enable, dragon eggs are hatching here.
* TODO-Cluster
*/
export const wipClusterEnable = async (): Promise<boolean> =>
(!!process.env.NEXT_PUBLIC_ENTE_WIP_CL && isDevBuild) ||
(await isInternalUser());
export type MLStatus =
| { phase: "disabled" /* The ML remote flag is off */ }
| {
@@ -539,16 +547,25 @@ export const peopleSubscribe = (onChange: () => void): (() => void) => {
};
};
/**
* If ML is enabled, set the people snapshot to an empty array to indicate that
* ML is enabled, but we're still reading in the set of people.
*
* Otherwise, if ML is disabled, set the people snapshot to `undefined`.
*/
const resetPeopleSnapshot = () =>
setPeopleSnapshot(_state.isMLEnabled ? [] : undefined);
/**
* Return the last known, cached {@link people}.
*
* This, along with {@link peopleSnapshot}, is meant to be used as arguments to
* React's {@link useSyncExternalStore}.
*
* A return value of `undefined` indicates that we're either still loading the
* initial list of people, or that the user has ML disabled and thus doesn't
* have any people (this is distinct from the case where the user has ML enabled
* but doesn't have any named "person" clusters so far).
* A return value of `undefined` indicates that ML is disabled. In all other
* cases, the list will be either empty (if we're either still loading the
* initial list of people, or if the user doesn't have any people), or, well,
* non-empty.
*/
export const peopleSnapshot = () => _state.peopleSnapshot;
@@ -589,19 +606,72 @@ export const clipMatches = (
): Promise<CLIPMatches | undefined> =>
worker().then((w) => w.clipMatches(searchPhrase));
/** A face ID annotated with the ID of the person to which it is associated. */
export interface AnnotatedFaceID {
faceID: string;
personID: string;
}
/**
* Return the IDs of all the faces in the given {@link enteFile} that are not
* associated with a person cluster.
* List of faces found in a file
*
* It is actually a pair of lists, one annotated by the person ids, and one with
* just the face ids.
*/
export const unidentifiedFaceIDs = async (
export interface AnnotatedFacesForFile {
/**
* A list of {@link AnnotatedFaceID}s for all faces in the file that are
* also associated with a {@link Person}.
*/
annotatedFaceIDs: AnnotatedFaceID[];
/* A list of the remaining face (ids). */
otherFaceIDs: string[];
}
/**
* Return the list of faces found in the given {@link enteFile}.
*/
export const getAnnotatedFacesForFile = async (
enteFile: EnteFile,
): Promise<string[]> => {
): Promise<AnnotatedFacesForFile> => {
const annotatedFaceIDs: AnnotatedFaceID[] = [];
const otherFaceIDs: string[] = [];
const index = await getFaceIndex(enteFile.id);
return index?.faces.map((f) => f.faceID) ?? [];
if (!index) return { annotatedFaceIDs, otherFaceIDs };
const people = _state.peopleSnapshot ?? [];
const faceIDToPersonID = new Map<string, string>();
for (const person of people) {
let faceIDs: string[];
if (person.type == "cgroup") {
faceIDs = person.cgroup.data.assigned.map((c) => c.faces).flat();
} else {
faceIDs = person.cluster.faces;
}
for (const faceID of faceIDs) {
faceIDToPersonID.set(faceID, person.id);
}
}
for (const { faceID } of index.faces) {
const personID = faceIDToPersonID.get(faceID);
if (personID) {
annotatedFaceIDs.push({ faceID, personID });
} else {
otherFaceIDs.push(faceID);
}
}
return { annotatedFaceIDs, otherFaceIDs };
};
/**
* Return the cached face crop for the given face, regenerating it if needed.
* Return a URL to the face crop for the given face, regenerating it if needed.
*
* The resultant URL is cached (both the object URL itself, and the underlying
* file crop blob used to generete it).
*
* @param faceID The id of the face whose face crop we want.
*
@@ -617,8 +687,17 @@ export const faceCrop = async (faceID: string, enteFile: EnteFile) => {
await inFlight;
const cache = await blobCache("face-crops");
return cache.get(faceID);
let url = _state.faceCropObjectURLCache.get(faceID);
if (!url) {
const cache = await blobCache("face-crops");
const blob = await cache.get(faceID);
if (blob) {
url = URL.createObjectURL(blob);
if (url) _state.faceCropObjectURLCache.set(faceID, url);
}
}
return url;
};
/**

View File

@@ -1,4 +1,3 @@
import { wipClusterEnable } from ".";
import type { EnteFile } from "../../types/file";
import { getLocalFiles } from "../files";
import { savedCGroups, type CGroup } from "../user-entity";
@@ -138,8 +137,6 @@ export type Person = (
* reference.
*/
export const reconstructPeople = async (): Promise<Person[]> => {
if (!(await wipClusterEnable())) return [];
const files = await getLocalFiles("normal");
const fileByID = new Map(files.map((f) => [f.id, f]));
@@ -164,6 +161,20 @@ export const reconstructPeople = async (): Promise<Person[]> => {
}
}
// Return annotated "person faces" corresponding to the given face ids,
// sorting them by the creation time of the file they belong to.
//
// Within the same file, sort by the face score.
const personFacesSortedNewestFirst = (faceIDs: string[]) =>
faceIDs
.map((faceID) => personFaceByID.get(faceID))
.filter((pf) => !!pf)
.sort((a, b) => {
const at = a.file.metadata.creationTime;
const bt = b.file.metadata.creationTime;
return bt == at ? b.score - a.score : bt - at;
});
// Help out tsc.
type Interim = (Person | undefined)[];
@@ -177,18 +188,19 @@ export const reconstructPeople = async (): Promise<Person[]> => {
// in the UI.
if (isHidden) return undefined;
// Older versions of the mobile app marked hidden cgroups by setting
// their name to an empty string.
if (!name) return undefined;
// Person faces from all the clusters assigned to this cgroup, sorted by
// their score.
const faces = assigned
.map(({ faces }) =>
faces.map((id) => personFaceByID.get(id)).filter((f) => !!f),
)
.flat()
.sort((a, b) => b.score - a.score);
// recency (then score).
const faces = personFacesSortedNewestFirst(
assigned.map(({ faces }) => faces).flat(),
);
// Ignore this cgroup if we don't have visible faces left in it.
const highestScoringFace = faces[0];
if (!highestScoringFace) return undefined;
const mostRecentFace = faces[0];
if (!mostRecentFace) return undefined;
// IDs of the files containing this face.
const fileIDs = [...new Set(faces.map((f) => f.file.id))];
@@ -207,8 +219,8 @@ export const reconstructPeople = async (): Promise<Person[]> => {
displayFaceID = avatarFaceID;
displayFaceFile = avatarFile;
} else {
displayFaceID = highestScoringFace.faceID;
displayFaceFile = highestScoringFace.file;
displayFaceID = mostRecentFace.faceID;
displayFaceFile = mostRecentFace.file;
}
return {
@@ -225,32 +237,32 @@ export const reconstructPeople = async (): Promise<Person[]> => {
// Convert local-only clusters to people.
const localClusters = await savedFaceClusters();
const clusterPeople: Interim = localClusters.map((cluster) => {
const faces = cluster.faces
.map((id) => personFaceByID.get(id))
.filter((f) => !!f);
const faces = personFacesSortedNewestFirst(cluster.faces);
// Ignore this cluster if we don't have visible faces left in it.
const mostRecentFace = faces[0];
if (!mostRecentFace) return undefined;
// Ignore clusters with too few visible faces.
if (faces.length < 10) return undefined;
const topFace = faces.reduce((top, face) =>
top.score > face.score ? top : face,
);
return {
type: "cluster",
cluster,
id: cluster.id,
name: undefined,
fileIDs: [...new Set(faces.map((f) => f.file.id))],
displayFaceID: topFace.faceID,
displayFaceFile: topFace.file,
displayFaceID: mostRecentFace.faceID,
displayFaceFile: mostRecentFace.file,
};
});
return cgroupPeople
.concat(clusterPeople)
.filter((c) => !!c)
.sort((a, b) => b.fileIDs.length - a.fileIDs.length);
const sorted = (ps: Interim) =>
ps
.filter((c) => !!c)
.sort((a, b) => b.fileIDs.length - a.fileIDs.length);
return sorted(cgroupPeople).concat(sorted(clusterPeople));
};
/**

View File

@@ -13,6 +13,19 @@ export interface MLWorkerDelegate {
* indicating the indexing or clustering status to be updated.
*/
workerDidUpdateStatus: () => void;
/**
* Called when the worker indexes some files, but then notices that the main
* thread was not awaiting the indexing (e.g. it was not initiated by the
* main thread during a sync, but happened because of a live upload).
*
* In such cases, it uses this method to inform the main thread that some
* files were indexed, so that it can update any dependent state (e.g.
* clusters).
*
* It doesn't always call this because otherwise the main thread would need
* some extra code to avoid updating the dependent state twice.
*/
workerDidUnawaitedIndex: () => void;
}
/**

View File

@@ -109,7 +109,13 @@ export class MLWorker {
private liveQ: IndexableItem[] = [];
private idleTimeout: ReturnType<typeof setTimeout> | undefined;
private idleDuration = idleDurationStart; /* unit: seconds */
private onNextIdles: (() => void)[] = [];
/** Resolvers for pending promises returned from calls to {@link index}. */
private onNextIdles: ((count: number) => void)[] = [];
/**
* Number of items processed since the last time {@link onNextIdles} was
* drained.
*/
private countSinceLastIdle = 0;
/**
* Initialize a new {@link MLWorker}.
@@ -140,9 +146,12 @@ export class MLWorker {
* During a backfill, we first attempt to fetch ML data for files which
* don't have that data locally. If on fetching we find what we need, we
* save it locally. Otherwise we index them.
*
* @return The count of items processed since the last last time we were
* idle.
*/
index() {
const nextIdle = new Promise<void>((resolve) =>
const nextIdle = new Promise<number>((resolve) =>
this.onNextIdles.push(resolve),
);
this.wakeUp();
@@ -225,17 +234,23 @@ export class MLWorker {
// Use the liveQ if present, otherwise get the next batch to backfill.
const items = liveQ.length ? liveQ : await this.backfillQ();
const allSuccess = await indexNextBatch(
items,
ensure(this.electron),
this.delegate,
);
if (allSuccess) {
// Everything is running smoothly. Reset the idle duration.
this.idleDuration = idleDurationStart;
// And tick again.
scheduleTick();
return;
this.countSinceLastIdle += items.length;
// If there is items remaining,
if (items.length > 0) {
// Index them.
const allSuccess = await indexNextBatch(
items,
ensure(this.electron),
this.delegate,
);
if (allSuccess) {
// Everything is running smoothly. Reset the idle duration.
this.idleDuration = idleDurationStart;
// And tick again.
scheduleTick();
return;
}
}
// We come here in three scenarios - either there is nothing left to do,
@@ -255,8 +270,16 @@ export class MLWorker {
// Resolve any awaiting promises returned from `index`.
const onNextIdles = this.onNextIdles;
const countSinceLastIdle = this.countSinceLastIdle;
this.onNextIdles = [];
onNextIdles.forEach((f) => f());
this.countSinceLastIdle = 0;
onNextIdles.forEach((f) => f(countSinceLastIdle));
// If no one was waiting, then let the main thread know via a different
// channel so that it can update the clusters and people.
if (onNextIdles.length == 0 && countSinceLastIdle > 0) {
this.delegate?.workerDidUnawaitedIndex();
}
}
/** Return the next batch of items to backfill (if any). */
@@ -321,13 +344,13 @@ export class MLWorker {
expose(MLWorker);
/**
* Find out files which need to be indexed. Then index the next batch of them.
* Index the given batch of items.
*
* Returns `false` to indicate that either an error occurred, or there are no
* more files to process, or that we cannot currently process files.
* Returns `false` to indicate that either an error occurred, or that we cannot
* currently process files since we don't have network connectivity.
*
* Which means that when it returns true, all is well and there are more
* things pending to process, so we should chug along at full speed.
* Which means that when it returns true, all is well and if there are more
* things pending to process, we should chug along at full speed.
*/
const indexNextBatch = async (
items: IndexableItem[],
@@ -342,9 +365,6 @@ const indexNextBatch = async (
return false;
}
// Nothing to do.
if (items.length == 0) return false;
// Keep track if any of the items failed.
let allSuccess = true;

View File

@@ -68,7 +68,7 @@ function RecoveryKey({ somethingWentWrong, ...props }: Props) {
fullWidth
>
<DialogTitleWithCloseButton onClose={props.onHide}>
{t("RECOVERY_KEY")}
{t("recovery_key")}
</DialogTitleWithCloseButton>
<DialogContent>
<Typography mb={3}>{t("RECOVERY_KEY_DESCRIPTION")}</Typography>
@@ -81,10 +81,10 @@ function RecoveryKey({ somethingWentWrong, ...props }: Props) {
</DialogContent>
<DialogActions>
<Button color="secondary" size="large" onClick={props.onHide}>
{t("SAVE_LATER")}
{t("do_this_later")}
</Button>
<Button color="accent" size="large" onClick={onSaveClick}>
{t("SAVE")}
{t("save_key")}
</Button>
</DialogActions>
</Dialog>