diff --git a/auth/windows/packaging/exe/inno_setup.iss b/auth/windows/packaging/exe/inno_setup.iss index 5906ecbd0e..653f293264 100644 --- a/auth/windows/packaging/exe/inno_setup.iss +++ b/auth/windows/packaging/exe/inno_setup.iss @@ -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] diff --git a/desktop/CHANGELOG.md b/desktop/CHANGELOG.md index 27ed96ab16..227c909512 100644 --- a/desktop/CHANGELOG.md +++ b/desktop/CHANGELOG.md @@ -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. diff --git a/desktop/package.json b/desktop/package.json index 77b014b9e6..683d74a2db 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -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", diff --git a/docs/docs/photos/features/machine-learning.md b/docs/docs/photos/features/machine-learning.md index 1b4355592f..640fe0f535 100644 --- a/docs/docs/photos/features/machine-learning.md +++ b/docs/docs/photos/features/machine-learning.md @@ -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. diff --git a/mobile/lib/services/machine_learning/ml_service.dart b/mobile/lib/services/machine_learning/ml_service.dart index b384c7286e..c09b6874bd 100644 --- a/mobile/lib/services/machine_learning/ml_service.dart +++ b/mobile/lib/services/machine_learning/ml_service.dart @@ -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) { diff --git a/mobile/lib/services/magic_cache_service.dart b/mobile/lib/services/magic_cache_service.dart index 3178e8c4ca..ecb4c4629f 100644 --- a/mobile/lib/services/magic_cache_service.dart +++ b/mobile/lib/services/magic_cache_service.dart @@ -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"); } diff --git a/mobile/lib/utils/file_uploader_util.dart b/mobile/lib/utils/file_uploader_util.dart index 6106307fd2..d06234bcf6 100644 --- a/mobile/lib/utils/file_uploader_util.dart +++ b/mobile/lib/utils/file_uploader_util.dart @@ -342,7 +342,8 @@ Future _getMediaUploadDataFromAppCache(EnteFile file) async { Map? 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 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; diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx index 15b64f6447..1360ee1cec 100644 --- a/web/apps/auth/src/pages/auth.tsx +++ b/web/apps/auth/src/pages/auth.tsx @@ -158,7 +158,7 @@ const AuthNavbar: React.FC = () => { startIcon={} onClick={logout} > - {t("LOGOUT")} + {t("logout")} diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 0e9c83dde4..3fe4db7fed 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -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 = ({ shouldHide, + showPeopleSectionButton, mode, onChangeMode, collectionSummaries, @@ -95,7 +95,7 @@ export const GalleryBarAndListHeader: React.FC = ({ setActiveCollectionID, hiddenCollectionSummaries, people, - activePersonID, + activePerson, onSelectPerson, setCollectionNamerAttributes, setPhotoListHeader, @@ -171,14 +171,13 @@ export const GalleryBarAndListHeader: React.FC = ({ } onCollectionCast={() => setOpenAlbumCastDialog(true)} /> - ) : ( + ) : activePerson ? ( p.id == activePersonID) ?? - people[0], - )} + person={activePerson} {...{ onSelectPerson, appContext }} /> + ) : ( + <> ), itemType: ITEM_TYPE.HEADER, height: 68, @@ -189,8 +188,7 @@ export const GalleryBarAndListHeader: React.FC = ({ toShowCollectionSummaries, activeCollectionID, isActiveCollectionDownloadInProgress, - people, - activePersonID, + activePerson, ]); if (shouldBeHidden) { @@ -201,11 +199,12 @@ export const GalleryBarAndListHeader: React.FC = ({ <> )} {(!step || step == "completed-with-errors") && ( diff --git a/web/apps/photos/src/components/PhotoFrame.tsx b/web/apps/photos/src/components/PhotoFrame.tsx index 59bb80b954..ccf0908a2b 100644 --- a/web/apps/photos/src/components/PhotoFrame.tsx +++ b/web/apps/photos/src/components/PhotoFrame.tsx @@ -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(0); @@ -580,6 +582,7 @@ const PhotoFrame = ({ setFilesDownloadProgressAttributesCreator={ setFilesDownloadProgressAttributesCreator } + onSelectPerson={onSelectPerson} /> ); diff --git a/web/apps/photos/src/components/PhotoList/index.tsx b/web/apps/photos/src/components/PhotoList/index.tsx index 2b879faf9a..e9777a8166 100644 --- a/web/apps/photos/src/components/PhotoList/index.tsx +++ b/web/apps/photos/src/components/PhotoList/index.tsx @@ -510,7 +510,7 @@ export function PhotoList({ itemType: ITEM_TYPE.OTHER, item: ( -
{t("NOTHING_HERE")}
+
{t("nothing_here")}
), id: "empty-list-banner", diff --git a/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx b/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx index 20157caab7..a528aa1c2e 100644 --- a/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx @@ -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; collectionNameMap?: Map; showCollectionChips: boolean; + /** + * Called when the user selects a person in the file info panel. + */ + onSelectPerson?: ((personID: string) => void) | undefined; } export const FileInfo: React.FC = ({ @@ -87,6 +99,7 @@ export const FileInfo: React.FC = ({ collectionNameMap, showCollectionChips, closePhotoViewer, + onSelectPerson, }) => { const { mapEnabled, updateMapEnabled, setDialogBoxAttributesV2 } = useContext(AppContext); @@ -97,6 +110,9 @@ export const FileInfo: React.FC = ({ const [exifInfo, setExifInfo] = useState(); 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 = ({ 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 = ({ getMapDisableConfirmationDialog(() => updateMapEnabled(false)), ); + const handleSelectFace = (annotatedFaceID: AnnotatedFaceID) => { + if (onSelectPerson) { + onSelectPerson(annotatedFaceID.personID); + closePhotoViewer(); + } + }; + return ( @@ -267,10 +305,17 @@ export const FileInfo: React.FC = ({ )} - {isMLEnabled() && ( + {isMLEnabled() && annotatedFaces && ( <> - {/* TODO-Cluster */} - + + )} diff --git a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx index b63c6c0c22..5674984e8e 100644 --- a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx @@ -658,7 +658,7 @@ const ImageEditorOverlay = (props: IProps) => { /> - + ({ 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; collectionNameMap: Map; 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) { ))}
diff --git a/web/apps/photos/src/components/Upload/UploadProgress/index.tsx b/web/apps/photos/src/components/Upload/UploadProgress/index.tsx index 8b12298760..a2259c9c2a 100644 --- a/web/apps/photos/src/components/Upload/UploadProgress/index.tsx +++ b/web/apps/photos/src/components/Upload/UploadProgress/index.tsx @@ -57,7 +57,7 @@ export default function UploadProgress({ action: props.cancelUploads, }, close: { - text: t("NO"), + text: t("no"), variant: "secondary", action: () => {}, }, diff --git a/web/apps/photos/src/components/UserNameInputDialog.tsx b/web/apps/photos/src/components/UserNameInputDialog.tsx index 8a709afa29..242fd357df 100644 --- a/web/apps/photos/src/components/UserNameInputDialog.tsx +++ b/web/apps/photos/src/components/UserNameInputDialog.tsx @@ -21,7 +21,7 @@ export default function UserNameInputDialog({ } /> - {t("ENTER_NAME")} + {t("enter_name")} diff --git a/web/apps/photos/src/components/pages/gallery/PlanSelector.tsx b/web/apps/photos/src/components/pages/gallery/PlanSelector.tsx index 0d6725a1a1..fe3c8f8cb6 100644 --- a/web/apps/photos/src/components/pages/gallery/PlanSelector.tsx +++ b/web/apps/photos/src/components/pages/gallery/PlanSelector.tsx @@ -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", diff --git a/web/apps/photos/src/components/pages/gallery/SelectedFileOptions.tsx b/web/apps/photos/src/components/pages/gallery/SelectedFileOptions.tsx index b9f133a2d0..fe4d4ffdaa 100644 --- a/web/apps/photos/src/components/pages/gallery/SelectedFileOptions.tsx +++ b/web/apps/photos/src/components/pages/gallery/SelectedFileOptions.tsx @@ -191,7 +191,7 @@ const SelectedFileOptions = ({ - + @@ -225,7 +225,7 @@ const SelectedFileOptions = ({ - + @@ -328,7 +328,7 @@ const SelectedFileOptions = ({ - + diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 32adaf4d61..4a18a425cc 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -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
; } + // `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 ( + ) : !isInSearchMode && + !isFirstLoad && + barMode == "people" && + !galleryPeopleState?.activePerson ? ( + ) : ( )} {selected.count > 0 && diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index 635685b7e3..b38cb55b5b 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -318,9 +318,9 @@ const Slideshow: React.FC = () => { /images/onboarding-lock/3x.png 3x" /> - + - {t("HERO_SLIDE_1")} + {t("intro_slide_1")} @@ -330,9 +330,9 @@ const Slideshow: React.FC = () => { /images/onboarding-safe/3x.png 3x" /> - + - {t("HERO_SLIDE_2")} + {t("intro_slide_2")} @@ -343,9 +343,9 @@ const Slideshow: React.FC = () => { /images/onboarding-sync/3x.png 3x" /> - + - {t("HERO_SLIDE_3")} + {t("intro_slide_3")} diff --git a/web/apps/photos/src/utils/ui/index.tsx b/web/apps/photos/src/utils/ui/index.tsx index 0e8d72e8f6..ecd25edb64 100644 --- a/web/apps/photos/src/utils/ui/index.tsx +++ b/web/apps/photos/src/utils/ui/index.tsx @@ -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") }, diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 757a1af60d..531f37763f 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -99,7 +99,7 @@ const Page: React.FC = ({ appContext }) => { const showNoRecoveryKeyMessage = () => setDialogBoxAttributesV2({ - title: t("SORRY"), + title: t("sorry"), close: {}, content: t("NO_RECOVERY_KEY_MESSAGE"), }); diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index 2da68bbe10..ab7492e443 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -149,7 +149,7 @@ const Page: React.FC = ({ appContext, twoFactorType }) => { dialogClose?: DialogBoxAttributesV2["close"], ) => { appContext.setDialogBoxAttributesV2({ - title: t("CONTACT_SUPPORT"), + title: t("contact_support"), close: dialogClose ?? {}, content: ( = ({ appContext }) => { const { logout, showNavBar, setDialogBoxAttributesV2 } = appContext; @@ -58,16 +59,9 @@ const Page: React.FC = ({ 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 = ({ 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; +}; diff --git a/web/packages/base/locales/ar-SA/translation.json b/web/packages/base/locales/ar-SA/translation.json index 14e8241547..839221eafe 100644 --- a/web/packages/base/locales/ar-SA/translation.json +++ b/web/packages/base/locales/ar-SA/translation.json @@ -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": "جديد في 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": "", diff --git a/web/packages/base/locales/bg-BG/translation.json b/web/packages/base/locales/bg-BG/translation.json index 8d89f0718c..0fa8cd3784 100644 --- a/web/packages/base/locales/bg-BG/translation.json +++ b/web/packages/base/locales/bg-BG/translation.json @@ -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": "", diff --git a/web/packages/base/locales/ca-ES/translation.json b/web/packages/base/locales/ca-ES/translation.json index a2df95ea52..41201c66d5 100644 --- a/web/packages/base/locales/ca-ES/translation.json +++ b/web/packages/base/locales/ca-ES/translation.json @@ -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": "", diff --git a/web/packages/base/locales/da-DK/translation.json b/web/packages/base/locales/da-DK/translation.json new file mode 100644 index 0000000000..41201c66d5 --- /dev/null +++ b/web/packages/base/locales/da-DK/translation.json @@ -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": "" +} diff --git a/web/packages/base/locales/de-DE/translation.json b/web/packages/base/locales/de-DE/translation.json index 8d4db06fe8..ced0ae23a3 100644 --- a/web/packages/base/locales/de-DE/translation.json +++ b/web/packages/base/locales/de-DE/translation.json @@ -1,15 +1,15 @@ { - "HERO_SLIDE_1_TITLE": "
Private Sicherungen
für deine Erinnerungen
", - "HERO_SLIDE_1": "Standardmäßig Ende-zu-Ende verschlüsselt", - "HERO_SLIDE_2_TITLE": "
Sicher gespeichert
in einem Luftschutzbunker
", - "HERO_SLIDE_2": "Entwickelt, um zu überleben", - "HERO_SLIDE_3_TITLE": "
Überall
verfügbar
", - "HERO_SLIDE_3": "Android, iOS, Web, Desktop", + "intro_slide_1_title": "
Private Sicherungen
für deine Erinnerungen
", + "intro_slide_1": "Standardmäßig Ende-zu-Ende verschlüsselt", + "intro_slide_2_title": "
Sicher gespeichert
in einem Luftschutzbunker
", + "intro_slide_2": "Entwickelt, um zu überleben", + "intro_slide_3_title": "
Überall
verfügbar
", + "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 {{emailID}} 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": "

Bitte sende eine E-Mail an {{emailID}} mit deiner registrierten E-Mail-Adresse.

Deine Anfrage wird innerhalb von 72 Stunden bearbeitet.

", - "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", diff --git a/web/packages/base/locales/el-GR/translation.json b/web/packages/base/locales/el-GR/translation.json index 999ebaa9c2..77837bc637 100644 --- a/web/packages/base/locales/el-GR/translation.json +++ b/web/packages/base/locales/el-GR/translation.json @@ -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": "Νέος/α στο 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": "Παρακαλώ αφήστε ένα μήνυμα ηλ. ταχυδρομείου στο {{emailID}} από την καταχωρημένη διεύθυνση σας", - "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": "", diff --git a/web/packages/base/locales/en-US/translation.json b/web/packages/base/locales/en-US/translation.json index 6e6ac74575..70e2b93a31 100644 --- a/web/packages/base/locales/en-US/translation.json +++ b/web/packages/base/locales/en-US/translation.json @@ -1,15 +1,15 @@ { - "HERO_SLIDE_1_TITLE": "
Private backups
for your memories
", - "HERO_SLIDE_1": "End-to-end encrypted by default", - "HERO_SLIDE_2_TITLE": "
Safely stored
at a fallout shelter
", - "HERO_SLIDE_2": "Designed to outlive", - "HERO_SLIDE_3_TITLE": "
Available
everywhere
", - "HERO_SLIDE_3": "Android, iOS, Web, Desktop", + "intro_slide_1_title": "
Private backups
for your memories
", + "intro_slide_1": "End-to-end encrypted by default", + "intro_slide_2_title": "
Safely stored
at a fallout shelter
", + "intro_slide_2": "Designed to outlive", + "intro_slide_3_title": "
Available
everywhere
", + "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 {{emailID}} 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": "

Please send an email to {{emailID}} from your registered email address.

Your request will be processed within 72 hours.

", - "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", diff --git a/web/packages/base/locales/es-ES/translation.json b/web/packages/base/locales/es-ES/translation.json index 36a5f58457..00167838fa 100644 --- a/web/packages/base/locales/es-ES/translation.json +++ b/web/packages/base/locales/es-ES/translation.json @@ -1,15 +1,15 @@ { - "HERO_SLIDE_1_TITLE": "
Copias de seguridad privadas
para su recuerdos
", - "HERO_SLIDE_1": "Encriptado de extremo a extremo por defecto", - "HERO_SLIDE_2_TITLE": "
Almacenado de forma segura
en un refugio de llenos
", - "HERO_SLIDE_2": "Diseñado para superar", - "HERO_SLIDE_3_TITLE": "
Disponible
en todas partes
", - "HERO_SLIDE_3": "Android, iOS, web, computadora", + "intro_slide_1_title": "
Copias de seguridad privadas
para su recuerdos
", + "intro_slide_1": "Encriptado de extremo a extremo por defecto", + "intro_slide_2_title": "
Almacenado de forma segura
en un refugio de llenos
", + "intro_slide_2": "Diseñado para superar", + "intro_slide_3_title": "
Disponible
en todas partes
", + "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 {{emailID}} 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": "

Por favor, envíe un email a {{emailID}} desde su dirección de correo electrónico registrada

Su solicitud será procesada en 72 horas.

", - "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": "", diff --git a/web/packages/base/locales/et-EE/translation.json b/web/packages/base/locales/et-EE/translation.json index a2df95ea52..41201c66d5 100644 --- a/web/packages/base/locales/et-EE/translation.json +++ b/web/packages/base/locales/et-EE/translation.json @@ -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": "", diff --git a/web/packages/base/locales/fa-IR/translation.json b/web/packages/base/locales/fa-IR/translation.json index 2b2266a34d..553f25f16e 100644 --- a/web/packages/base/locales/fa-IR/translation.json +++ b/web/packages/base/locales/fa-IR/translation.json @@ -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": "", diff --git a/web/packages/base/locales/fi-FI/translation.json b/web/packages/base/locales/fi-FI/translation.json index 22ef432c06..f49794ef69 100644 --- a/web/packages/base/locales/fi-FI/translation.json +++ b/web/packages/base/locales/fi-FI/translation.json @@ -1,15 +1,15 @@ { - "HERO_SLIDE_1_TITLE": "
Yksityiset varmuuskopiot
muistoillesi
", - "HERO_SLIDE_1": "Päästä päähän -salaus käytössä oletuksena", - "HERO_SLIDE_2_TITLE": "
Turvallisesti varastoitu
väestönsuojan tiloissa
", - "HERO_SLIDE_2": "", - "HERO_SLIDE_3_TITLE": "
Saatavilla
kaikkialla
", - "HERO_SLIDE_3": "Android, iOS, Web, Tietokone", + "intro_slide_1_title": "
Yksityiset varmuuskopiot
muistoillesi
", + "intro_slide_1": "Päästä päähän -salaus käytössä oletuksena", + "intro_slide_2_title": "
Turvallisesti varastoitu
väestönsuojan tiloissa
", + "intro_slide_2": "", + "intro_slide_3_title": "
Saatavilla
kaikkialla
", + "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": "", diff --git a/web/packages/base/locales/fr-FR/translation.json b/web/packages/base/locales/fr-FR/translation.json index d749ec9aaf..b39fc0513b 100644 --- a/web/packages/base/locales/fr-FR/translation.json +++ b/web/packages/base/locales/fr-FR/translation.json @@ -1,15 +1,15 @@ { - "HERO_SLIDE_1_TITLE": "
Sauvegardes privées
pour vos souvenirs
", - "HERO_SLIDE_1": "Chiffrement de bout en bout par défaut", - "HERO_SLIDE_2_TITLE": "
Sécurisé
dans un abri antiatomique
", - "HERO_SLIDE_2": "Conçu pour survivre", - "HERO_SLIDE_3_TITLE": "
Disponible
en tout lieu
", - "HERO_SLIDE_3": "Android, iOS, Web, Ordinateur", + "intro_slide_1_title": "
Sauvegardes privées
pour vos souvenirs
", + "intro_slide_1": "Chiffrement de bout en bout par défaut", + "intro_slide_2_title": "
Sécurisé
dans un abri antiatomique
", + "intro_slide_2": "Conçu pour survivre", + "intro_slide_3_title": "
Disponible
en tout lieu
", + "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 à {{emailID}} 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": "

Veuillez envoyer un e-mail à {{emailID}}depuis Votre adresse enregistrée.

Votre demande sera traitée dans les 72 heures.

", - "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é", diff --git a/web/packages/base/locales/gu-IN/translation.json b/web/packages/base/locales/gu-IN/translation.json index a2df95ea52..41201c66d5 100644 --- a/web/packages/base/locales/gu-IN/translation.json +++ b/web/packages/base/locales/gu-IN/translation.json @@ -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": "", diff --git a/web/packages/base/locales/hi-IN/translation.json b/web/packages/base/locales/hi-IN/translation.json index a2df95ea52..41201c66d5 100644 --- a/web/packages/base/locales/hi-IN/translation.json +++ b/web/packages/base/locales/hi-IN/translation.json @@ -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": "", diff --git a/web/packages/base/locales/id-ID/translation.json b/web/packages/base/locales/id-ID/translation.json index 51189f1ede..7cc9eabc0c 100644 --- a/web/packages/base/locales/id-ID/translation.json +++ b/web/packages/base/locales/id-ID/translation.json @@ -1,15 +1,15 @@ { - "HERO_SLIDE_1_TITLE": "
Cadangan pribadi
untuk kenanganmu
", - "HERO_SLIDE_1": "Dirancang dengan enkripsi ujung ke ujung", - "HERO_SLIDE_2_TITLE": "
Tersimpan aman
di tempat pengungsian
", - "HERO_SLIDE_2": "Dibuat untuk melestarikan", - "HERO_SLIDE_3_TITLE": "
Tersedia
di mana saja
", - "HERO_SLIDE_3": "Android, iOS, Web, Desktop", + "intro_slide_1_title": "
Cadangan pribadi
untuk kenanganmu
", + "intro_slide_1": "Dirancang dengan enkripsi ujung ke ujung", + "intro_slide_2_title": "
Tersimpan aman
di tempat pengungsian
", + "intro_slide_2": "Dibuat untuk melestarikan", + "intro_slide_3_title": "
Tersedia
di mana saja
", + "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 {{emailID}} 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": "", diff --git a/web/packages/base/locales/is-IS/translation.json b/web/packages/base/locales/is-IS/translation.json index 1e940d36f3..e712369fd3 100644 --- a/web/packages/base/locales/is-IS/translation.json +++ b/web/packages/base/locales/is-IS/translation.json @@ -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": "", diff --git a/web/packages/base/locales/it-IT/translation.json b/web/packages/base/locales/it-IT/translation.json index 57596a5920..7ba18ce17d 100644 --- a/web/packages/base/locales/it-IT/translation.json +++ b/web/packages/base/locales/it-IT/translation.json @@ -1,15 +1,15 @@ { - "HERO_SLIDE_1_TITLE": "
Backup privati
dei tuoi ricordi
", - "HERO_SLIDE_1": "Crittografia end-to-end", - "HERO_SLIDE_2_TITLE": "
Salvati in modo sicuro
in un rifugio antiatomico
", - "HERO_SLIDE_2": "Progettato per sopravvivere", - "HERO_SLIDE_3_TITLE": "
Disponibile
ovunque
", - "HERO_SLIDE_3": "Android, iOS, Web, Desktop", + "intro_slide_1_title": "
Backup privati
dei tuoi ricordi
", + "intro_slide_1": "Crittografia end-to-end", + "intro_slide_2_title": "
Salvati in modo sicuro
in un rifugio antiatomico
", + "intro_slide_2": "Progettato per sopravvivere", + "intro_slide_3_title": "
Disponibile
ovunque
", + "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 {{emailID}} 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": "

Per favore invia una email a {{emailID}} dal tuo indirizzo email registrato.

La tua richiesta verrà elaborata entro 72 ore.

", - "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", diff --git a/web/packages/base/locales/ja-JP/translation.json b/web/packages/base/locales/ja-JP/translation.json index a2df95ea52..41201c66d5 100644 --- a/web/packages/base/locales/ja-JP/translation.json +++ b/web/packages/base/locales/ja-JP/translation.json @@ -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": "", diff --git a/web/packages/base/locales/km-KH/translation.json b/web/packages/base/locales/km-KH/translation.json index a2df95ea52..41201c66d5 100644 --- a/web/packages/base/locales/km-KH/translation.json +++ b/web/packages/base/locales/km-KH/translation.json @@ -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": "", diff --git a/web/packages/base/locales/ko-KR/translation.json b/web/packages/base/locales/ko-KR/translation.json index 57ec280f84..cf536867fb 100644 --- a/web/packages/base/locales/ko-KR/translation.json +++ b/web/packages/base/locales/ko-KR/translation.json @@ -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": "안드로이드, iOS, 웹, 데스크탑", + "intro_slide_1_title": "
당신의 추억을 위한
비공개 백업
", + "intro_slide_1": "종단간 암호화를 기본적으로 지원합니다", + "intro_slide_2_title": "
낙진 대피소에
안전하게 보관됨
", + "intro_slide_2": "장기 보존을 위해 설계되었습니다", + "intro_slide_3_title": "
모든 기기에서
사용 가능
", + "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": "

회원가입에 사용한 이메일을 통해 {{emailID}} (으)로 메일을 보내주세요.

귀하의 요청은 72시간 내로 처리됩니다.

", - "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": "", diff --git a/web/packages/base/locales/nl-NL/translation.json b/web/packages/base/locales/nl-NL/translation.json index 0b55aa1705..dc0197a84d 100644 --- a/web/packages/base/locales/nl-NL/translation.json +++ b/web/packages/base/locales/nl-NL/translation.json @@ -1,15 +1,15 @@ { - "HERO_SLIDE_1_TITLE": "
Privé back-ups
voor uw herinneringen
", - "HERO_SLIDE_1": "Standaard end-to-end versleuteld", - "HERO_SLIDE_2_TITLE": "
Veilig opgeslagen
in een kernbunker
", - "HERO_SLIDE_2": "Ontworpen om levenslang mee te gaan", - "HERO_SLIDE_3_TITLE": "
Overal
beschikbaar
", - "HERO_SLIDE_3": "Android, iOS, Web, Desktop", + "intro_slide_1_title": "
Privé back-ups
voor uw herinneringen
", + "intro_slide_1": "Standaard end-to-end versleuteld", + "intro_slide_2_title": "
Veilig opgeslagen
in een kernbunker
", + "intro_slide_2": "Ontworpen om levenslang mee te gaan", + "intro_slide_3_title": "
Overal
beschikbaar
", + "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 {{emailID}} 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": "

Stuur een e-mail naar {{emailID}} vanaf uw geregistreerde e-mailadres.

Uw aanvraag wordt binnen 72 uur verwerkt.

", - "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", diff --git a/web/packages/base/locales/pl-PL/translation.json b/web/packages/base/locales/pl-PL/translation.json index 05de0ab560..9463235f55 100644 --- a/web/packages/base/locales/pl-PL/translation.json +++ b/web/packages/base/locales/pl-PL/translation.json @@ -1,15 +1,15 @@ { - "HERO_SLIDE_1_TITLE": "
Prywatne kopie zapasowe
dla Twoich wspomnień
", - "HERO_SLIDE_1": "Domyślnie zaszyfrowane metodą end-to-end", - "HERO_SLIDE_2_TITLE": "
Bezpiecznie przechowywane
w awaryjnym schronieniu
", - "HERO_SLIDE_2": "Zaprojektowane do przetrwania", - "HERO_SLIDE_3_TITLE": "
Dostępne
wszędzie
", - "HERO_SLIDE_3": "Android, iOS, Strona Internetowa, Aplikacja Komputerowa", + "intro_slide_1_title": "
Prywatne kopie zapasowe
dla Twoich wspomnień
", + "intro_slide_1": "Domyślnie zaszyfrowane metodą end-to-end", + "intro_slide_2_title": "
Bezpiecznie przechowywane
w awaryjnym schronieniu
", + "intro_slide_2": "Zaprojektowane do przetrwania", + "intro_slide_3_title": "
Dostępne
wszędzie
", + "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 {{emailID}} 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": "

Prosimy wysłać wiadomość e-mail na {{emailID}} z Twojego zarejestrowanego adresu e-mail.

Twoja prośba zostanie przetworzona w ciągu 72 godzin.

", - "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", diff --git a/web/packages/base/locales/pt-BR/translation.json b/web/packages/base/locales/pt-BR/translation.json index af2910ed90..a7493b2f95 100644 --- a/web/packages/base/locales/pt-BR/translation.json +++ b/web/packages/base/locales/pt-BR/translation.json @@ -1,15 +1,15 @@ { - "HERO_SLIDE_1_TITLE": "
Backups privados
para as suas memórias
", - "HERO_SLIDE_1": "Criptografia de ponta a ponta por padrão", - "HERO_SLIDE_2_TITLE": "
Armazenado com segurança
em um abrigo avançado
", - "HERO_SLIDE_2": "Feito para ter longevidade", - "HERO_SLIDE_3_TITLE": "
Disponível
em qualquer lugar
", - "HERO_SLIDE_3": "Android, iOS, Web, Desktop", + "intro_slide_1_title": "
Backups privados
para as suas memórias
", + "intro_slide_1": "Criptografia de ponta a ponta por padrão", + "intro_slide_2_title": "
Armazenado com segurança
em um abrigo avançado
", + "intro_slide_2": "Feito para ter longevidade", + "intro_slide_3_title": "
Disponível
em qualquer lugar
", + "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 {{emailID}} 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": "

Por favor, envie um e-mail para {{emailID}} a partir do seu endereço de e-mail registrado.

Seu pedido será processado dentro de 72 horas.

", - "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", diff --git a/web/packages/base/locales/pt-PT/translation.json b/web/packages/base/locales/pt-PT/translation.json index 8ee320de07..9ab29e1de4 100644 --- a/web/packages/base/locales/pt-PT/translation.json +++ b/web/packages/base/locales/pt-PT/translation.json @@ -1,15 +1,15 @@ { - "HERO_SLIDE_1_TITLE": "
Backups privados
para as suas memórias
", - "HERO_SLIDE_1": "", - "HERO_SLIDE_2_TITLE": "", - "HERO_SLIDE_2": "", - "HERO_SLIDE_3_TITLE": "
Disponível
em qualquer lugar
", - "HERO_SLIDE_3": "Android, iOS, Web, Desktop", + "intro_slide_1_title": "
Backups privados
para as suas memórias
", + "intro_slide_1": "", + "intro_slide_2_title": "", + "intro_slide_2": "", + "intro_slide_3_title": "
Disponível
em qualquer lugar
", + "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": "", diff --git a/web/packages/base/locales/ru-RU/translation.json b/web/packages/base/locales/ru-RU/translation.json index 028da64e5c..9aefe0a128 100644 --- a/web/packages/base/locales/ru-RU/translation.json +++ b/web/packages/base/locales/ru-RU/translation.json @@ -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": "Android, iOS, Веб, ПК", + "intro_slide_1_title": "
Приватные резервные копии
для ваших воспоминаний
", + "intro_slide_1": "Сквозное шифрование по умолчанию", + "intro_slide_2_title": "
Надежно хранится
в убежище от радиоактивных осадков
", + "intro_slide_2": "Созданный для того, чтобы пережить", + "intro_slide_3_title": "
Доступно
везде
", + "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": "Пожалуйста, отправьте электронное письмо на адрес {{emailID}} с вашего зарегистрированного адреса электронной почты", - "CONTACT_SUPPORT": "Связаться с поддержкой", - "REQUEST_FEATURE": "Запросить функцию", - "SUPPORT": "Поддержка", - "CONFIRM": "Подтвердить", + "contact_support": "Связаться с поддержкой", + "request_feature": "Запросить функцию", + "support": "Поддержка", "cancel": "Отменить", - "LOGOUT": "Выйти", + "logout": "Выйти", + "logout_message": "Вы уверены, что хотите выйти?", "delete_account": "Удалить аккаунт", "delete_account_manually_message": "

Пожалуйста, отправьте письмо по адресу {{emailID}} с вашего зарегистрированного адреса электронной почты.

Ваш запрос будет обработан в течение 72 часов

", - "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": "Загрузка маршрута через близлежащие серверы", diff --git a/web/packages/base/locales/sv-SE/translation.json b/web/packages/base/locales/sv-SE/translation.json index f8861c9945..d75e8c0d08 100644 --- a/web/packages/base/locales/sv-SE/translation.json +++ b/web/packages/base/locales/sv-SE/translation.json @@ -1,15 +1,15 @@ { - "HERO_SLIDE_1_TITLE": "
Privata säkerhetskopior
för dina minnen
", - "HERO_SLIDE_1": "Totalsträckskryptering som standard", - "HERO_SLIDE_2_TITLE": "", - "HERO_SLIDE_2": "Utformad för att överleva", - "HERO_SLIDE_3_TITLE": "
Tillgänglig
överallt
", - "HERO_SLIDE_3": "Android, iOS, webb, skrivbord", + "intro_slide_1_title": "
Privata säkerhetskopior
för dina minnen
", + "intro_slide_1": "Totalsträckskryptering som standard", + "intro_slide_2_title": "", + "intro_slide_2": "Utformad för att överleva", + "intro_slide_3_title": "
Tillgänglig
överallt
", + "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": "", diff --git a/web/packages/base/locales/ta-IN/translation.json b/web/packages/base/locales/ta-IN/translation.json index a2df95ea52..41201c66d5 100644 --- a/web/packages/base/locales/ta-IN/translation.json +++ b/web/packages/base/locales/ta-IN/translation.json @@ -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": "", diff --git a/web/packages/base/locales/te-IN/translation.json b/web/packages/base/locales/te-IN/translation.json index a2df95ea52..41201c66d5 100644 --- a/web/packages/base/locales/te-IN/translation.json +++ b/web/packages/base/locales/te-IN/translation.json @@ -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": "", diff --git a/web/packages/base/locales/th-TH/translation.json b/web/packages/base/locales/th-TH/translation.json index a2df95ea52..41201c66d5 100644 --- a/web/packages/base/locales/th-TH/translation.json +++ b/web/packages/base/locales/th-TH/translation.json @@ -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": "", diff --git a/web/packages/base/locales/ti-ER/translation.json b/web/packages/base/locales/ti-ER/translation.json index a2df95ea52..41201c66d5 100644 --- a/web/packages/base/locales/ti-ER/translation.json +++ b/web/packages/base/locales/ti-ER/translation.json @@ -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": "", diff --git a/web/packages/base/locales/tr-TR/translation.json b/web/packages/base/locales/tr-TR/translation.json index faa930266e..e572e3098e 100644 --- a/web/packages/base/locales/tr-TR/translation.json +++ b/web/packages/base/locales/tr-TR/translation.json @@ -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 {{emailID}} 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": "

Lütfen kaydolduğun e-posta adresinden {{emailID}} adresine bir e-posta gönder.

İsteğin 72 saat içinde işleme alınacaktır.

", - "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": "", diff --git a/web/packages/base/locales/zh-CN/translation.json b/web/packages/base/locales/zh-CN/translation.json index 48b90a3d81..6ddafa5823 100644 --- a/web/packages/base/locales/zh-CN/translation.json +++ b/web/packages/base/locales/zh-CN/translation.json @@ -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": "安卓, iOS, 网页端, 桌面端", + "intro_slide_1_title": "
私人备份
为您的回忆
", + "intro_slide_1": "默认端到端加密", + "intro_slide_2_title": "
安全地存放
在一个掩护所中
", + "intro_slide_2": "经久耐用", + "intro_slide_3_title": "
可用于
各处
", + "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账户的电子邮箱发一封邮件给 {{emailID}}", - "CONTACT_SUPPORT": "联系支持", - "REQUEST_FEATURE": "功能建议", - "SUPPORT": "支持", - "CONFIRM": "确认", + "contact_support": "联系支持", + "request_feature": "功能建议", + "support": "支持", "cancel": "取消", - "LOGOUT": "退出登录", + "logout": "退出登录", + "logout_message": "你确定要退出登录吗?", "delete_account": "删除账户", "delete_account_manually_message": "

请从您注册的电子邮件地址发送一封电子邮件到 {{emailID}}

。您的请求将在72小时内处理。

", - "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": "通过附近的服务器路由上传", diff --git a/web/packages/new/photos/components/Gallery/BarImpl.tsx b/web/packages/new/photos/components/Gallery/BarImpl.tsx index 80a65e8dde..ac8ec9920d 100644 --- a/web/packages/new/photos/components/Gallery/BarImpl.tsx +++ b/web/packages/new/photos/components/Gallery/BarImpl.tsx @@ -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 = ({ + showPeopleSectionButton, mode, onChangeMode, collectionSummaries, @@ -116,7 +119,7 @@ export const GalleryBarImpl: React.FC = ({ collectionsSortBy, onChangeCollectionsSortBy, people, - activePersonID, + activePerson, onSelectPerson, }) => { const isMobile = useIsMobileWidth(); @@ -194,11 +197,11 @@ export const GalleryBarImpl: React.FC = ({ ); 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( () => @@ -210,12 +213,9 @@ export const GalleryBarImpl: React.FC = ({ 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 = ({ activeCollectionID, onSelectCollectionID, people, - activePersonID, + activePerson, onSelectPerson, ], ); - const controls1 = isMobile && ( + const controls1 = isMobile && mode != "people" && ( = ({ ); - const controls2 = !isMobile && ( + const controls2 = !isMobile && mode != "people" && ( = ({ ); return ( - + // Hide the bottom border if we're showing the empty state for people. + - + {controls1} @@ -314,33 +319,46 @@ export const Row2 = styled(Box)` `; const ModeIndicator: React.FC< - Pick -> = ({ 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 {t("hidden_albums")}; } - // 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 {t("albums")}; } return ( - onChangeMode("albums")}> + onChangeMode("albums")} + > {t("albums")} - - {t("people")} + + onChangeMode("people")} + > + {t("people")} + ); }; -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 = ({ onSelectPerson(person)} > {person.name && } - {activePerson.id === person.id && } + {activePerson?.id === person.id && } ); diff --git a/web/packages/new/photos/components/Gallery/PeopleHeader.tsx b/web/packages/new/photos/components/Gallery/PeopleHeader.tsx index 616ae53acd..f773f1f1c1 100644 --- a/web/packages/new/photos/components/Gallery/PeopleHeader.tsx +++ b/web/packages/new/photos/components/Gallery/PeopleHeader.tsx @@ -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 & { person: Person; appContext: NewAppContextPhotos; @@ -107,7 +128,7 @@ const CGroupPersonOptions: React.FC = ({ ), close: { text: t("cancel") }, proceed: { - text: t("RESET"), + text: t("reset"), action: doDeletePerson, }, buttonDirection: "row", @@ -151,8 +172,8 @@ const CGroupPersonOptions: React.FC = ({ 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 = ({ 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} /> diff --git a/web/packages/new/photos/components/Gallery/index.tsx b/web/packages/new/photos/components/Gallery/index.tsx index 34325da009..ddf14e4dea 100644 --- a/web/packages/new/photos/components/Gallery/index.tsx +++ b/web/packages/new/photos/components/Gallery/index.tsx @@ -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 = ({ /> ); + +export const PeopleEmptyState: React.FC = () => ( + + + +); diff --git a/web/packages/new/photos/components/ItemCards.tsx b/web/packages/new/photos/components/ItemCards.tsx index 95aab19609..8c73fb3ad6 100644 --- a/web/packages/new/photos/components/ItemCards.tsx +++ b/web/packages/new/photos/components/ItemCards.tsx @@ -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> = ({ TileComponent, coverFile, + coverFaceID, isScrolling, onClick, children, }) => { - const [coverImageURL, setCoverImageURL] = useState(""); + const [coverImageURL, setCoverImageURL] = useState(); 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 ( diff --git a/web/packages/new/photos/components/PeopleList.tsx b/web/packages/new/photos/components/PeopleList.tsx index 1f9862f308..ac41dc7deb 100644 --- a/web/packages/new/photos/components/PeopleList.tsx +++ b/web/packages/new/photos/components/PeopleList.tsx @@ -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 = ({ sx={{ justifyContent: people.length > 3 ? "center" : "start" }} > {people.slice(0, isMobileWidth ? 6 : 7).map((person) => ( - onSelectPerson(person)} > @@ -34,7 +35,7 @@ export const SearchPeopleList: React.FC = ({ enteFile={person.displayFaceFile} placeholderDimension={87} /> - + ))} ); @@ -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 ( + <> + + {t("people")} + + + {annotatedFaceIDs.map((annotatedFaceID) => ( + onSelectFace(annotatedFaceID)} + > + + + ))} + + + ); +}; + +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 = ({ +export const UnclusteredFaceList: React.FC = ({ enteFile, + faceIDs, }) => { - const [faceIDs, setFaceIDs] = useState([]); - - 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 ( <> - {t("UNIDENTIFIED_FACES")} + {pt("Other faces")} + {/*t("UNIDENTIFIED_FACES") TODO-Cluster */} - + {faceIDs.map((faceID) => ( - + - + ))} - + ); }; +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 = ({ enteFile, placeholderDimension, }) => { - const [objectURL, setObjectURL] = useState(); + const [url, setURL] = useState(); 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 ? ( - + return url ? ( + ) : ( [ 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(); - 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); } diff --git a/web/packages/new/photos/services/ml/index.ts b/web/packages/new/photos/services/ml/index.ts index ddb88dd69c..0c841e643d 100644 --- a/web/packages/new/photos/services/ml/index.ts +++ b/web/packages/new/photos/services/ml/index.ts @@ -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>(); + + /** + * Cached object URLs to face crops that we have previously vended out. + * + * The cache is only cleared on logout. + */ + faceCropObjectURLCache = new Map(); } /** 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 => - (!!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 => 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 => { +): Promise => { + 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(); + 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; }; /** diff --git a/web/packages/new/photos/services/ml/people.ts b/web/packages/new/photos/services/ml/people.ts index 6ad95d3fad..ef30e36707 100644 --- a/web/packages/new/photos/services/ml/people.ts +++ b/web/packages/new/photos/services/ml/people.ts @@ -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 => { - 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 => { } } + // 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 => { // 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 => { 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 => { // 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)); }; /** diff --git a/web/packages/new/photos/services/ml/worker-types.ts b/web/packages/new/photos/services/ml/worker-types.ts index b12598d52b..16153b3b2b 100644 --- a/web/packages/new/photos/services/ml/worker-types.ts +++ b/web/packages/new/photos/services/ml/worker-types.ts @@ -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; } /** diff --git a/web/packages/new/photos/services/ml/worker.ts b/web/packages/new/photos/services/ml/worker.ts index b9156e4106..42ee2a439f 100644 --- a/web/packages/new/photos/services/ml/worker.ts +++ b/web/packages/new/photos/services/ml/worker.ts @@ -109,7 +109,13 @@ export class MLWorker { private liveQ: IndexableItem[] = []; private idleTimeout: ReturnType | 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((resolve) => + const nextIdle = new Promise((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; diff --git a/web/packages/shared/components/RecoveryKey.tsx b/web/packages/shared/components/RecoveryKey.tsx index 53f8f2050f..6f10023942 100644 --- a/web/packages/shared/components/RecoveryKey.tsx +++ b/web/packages/shared/components/RecoveryKey.tsx @@ -68,7 +68,7 @@ function RecoveryKey({ somethingWentWrong, ...props }: Props) { fullWidth > - {t("RECOVERY_KEY")} + {t("recovery_key")} {t("RECOVERY_KEY_DESCRIPTION")} @@ -81,10 +81,10 @@ function RecoveryKey({ somethingWentWrong, ...props }: Props) {