diff --git a/cli/Dockerfile b/cli/Dockerfile index 827269f5f8..4e00a47645 100644 --- a/cli/Dockerfile +++ b/cli/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20-alpine3.17 as builder +FROM golang:1.20-alpine3.17 AS builder RUN apk add --no-cache gcc musl-dev git build-base pkgconfig libsodium-dev ENV GOOS=linux diff --git a/cli/Dockerfile-x86 b/cli/Dockerfile-x86 index 23a877cb9f..42ea9c90dc 100644 --- a/cli/Dockerfile-x86 +++ b/cli/Dockerfile-x86 @@ -1,4 +1,4 @@ -FROM golang:1.20-alpine3.17@sha256:9c2f89db6fda13c3c480749787f62fed5831699bb2c32881b8f327f1cf7bae42 as builder386 +FROM golang:1.20-alpine3.17@sha256:9c2f89db6fda13c3c480749787f62fed5831699bb2c32881b8f327f1cf7bae42 AS builder386 RUN apt-get update RUN apt-get install -y gcc RUN apt-get install -y git diff --git a/desktop/src/main.ts b/desktop/src/main.ts index be5721ed0e..2622d09a6e 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -537,7 +537,7 @@ const setupTrayItem = (mainWindow: BrowserWindow) => { * old cache dir if it exists. * * Added May 2024, v1.7.0. This migration code can be removed after some time - * once most people have upgraded to newer versions. + * once most people have upgraded to newer versions (tag: Migration). */ const deleteLegacyDiskCacheDirIfExists = async () => { const removeIfExists = async (dirPath: string) => { diff --git a/desktop/src/main/services/ml-clip.ts b/desktop/src/main/services/ml-clip.ts index ae3470d2c7..cea1d667b5 100644 --- a/desktop/src/main/services/ml-clip.ts +++ b/desktop/src/main/services/ml-clip.ts @@ -3,6 +3,7 @@ * * The embeddings are computed using ONNX runtime, with CLIP as the model. */ + import Tokenizer from "clip-bpe-js"; import * as ort from "onnxruntime-node"; import log from "../log"; diff --git a/desktop/src/main/services/ml-face.ts b/desktop/src/main/services/ml-face.ts index b6fb5c90f3..33c09efaa2 100644 --- a/desktop/src/main/services/ml-face.ts +++ b/desktop/src/main/services/ml-face.ts @@ -6,6 +6,7 @@ * * The runtime used is ONNX. */ + import * as ort from "onnxruntime-node"; import log from "../log"; import { ensure } from "../utils/common"; diff --git a/desktop/src/main/services/ml.ts b/desktop/src/main/services/ml.ts index 6b38bc74dc..55bb8d79c2 100644 --- a/desktop/src/main/services/ml.ts +++ b/desktop/src/main/services/ml.ts @@ -1,5 +1,5 @@ /** - * @file AI/ML related functionality, generic layer. + * @file ML related functionality, generic layer. * * @see also `ml-clip.ts`, `ml-face.ts`. * @@ -10,6 +10,7 @@ * can use the binary ONNX runtime which is 10-20x faster than the WASM based * web one. */ + import { app, net } from "electron/main"; import { existsSync } from "fs"; import fs from "node:fs/promises"; diff --git a/desktop/src/main/services/upload.ts b/desktop/src/main/services/upload.ts index 3da9cafd7c..a7e3fc8671 100644 --- a/desktop/src/main/services/upload.ts +++ b/desktop/src/main/services/upload.ts @@ -58,7 +58,7 @@ export const pendingUploads = async (): Promise => { const allZipItems = uploadStatusStore.get("zipItems"); let zipItems: typeof allZipItems; - // Migration code - May 2024. Remove after a bit. + // Migration code - May 2024. Remove after a bit (tag: Migration). // // The older store formats will not have zipItems and instead will have // zipPaths. If we find such a case, read the zipPaths and enqueue all of diff --git a/mobile/lib/generated/intl/messages_all.dart b/mobile/lib/generated/intl/messages_all.dart index aa2bd004dd..e274c0ccdb 100644 --- a/mobile/lib/generated/intl/messages_all.dart +++ b/mobile/lib/generated/intl/messages_all.dart @@ -28,6 +28,7 @@ import 'messages_no.dart' as messages_no; import 'messages_pl.dart' as messages_pl; import 'messages_pt.dart' as messages_pt; import 'messages_ru.dart' as messages_ru; +import 'messages_tr.dart' as messages_tr; import 'messages_zh.dart' as messages_zh; typedef Future LibraryLoader(); @@ -44,6 +45,7 @@ Map _deferredLibraries = { 'pl': () => new SynchronousFuture(null), 'pt': () => new SynchronousFuture(null), 'ru': () => new SynchronousFuture(null), + 'tr': () => new SynchronousFuture(null), 'zh': () => new SynchronousFuture(null), }; @@ -73,6 +75,8 @@ MessageLookupByLibrary? _findExact(String localeName) { return messages_pt.messages; case 'ru': return messages_ru.messages; + case 'tr': + return messages_tr.messages; case 'zh': return messages_zh.messages; default: diff --git a/mobile/lib/generated/intl/messages_tr.dart b/mobile/lib/generated/intl/messages_tr.dart new file mode 100644 index 0000000000..061041d69e --- /dev/null +++ b/mobile/lib/generated/intl/messages_tr.dart @@ -0,0 +1,1710 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that provides messages for a tr locale. All the +// messages from the main program should be duplicated here with the same +// function name. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new +// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering +// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases +// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; + +final messages = new MessageLookup(); + +typedef String MessageIfAbsent(String messageStr, List args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'tr'; + + static String m0(count) => + "${Intl.plural(count, zero: 'Ortak çalışan ekle', one: 'Ortak çalışan ekle', other: 'Ortak çalışan ekle')}"; + + static String m2(count) => + "${Intl.plural(count, one: 'Öğeyi taşı', other: 'Öğeleri taşı')}"; + + static String m3(storageAmount, endDate) => + "${storageAmount} eklentiniz ${endDate} tarihine kadar geçerlidir"; + + static String m1(count) => + "${Intl.plural(count, zero: 'Görüntüleyen ekle', one: 'Görüntüleyen ekle', other: 'Görüntüleyen ekle')}"; + + static String m4(emailOrName) => "${emailOrName} tarafından eklendi"; + + static String m5(albumName) => "${albumName} albümüne başarıyla eklendi"; + + static String m6(count) => + "${Intl.plural(count, zero: 'Katılımcı Yok', one: '1 Katılımcı', other: '${count} Katılımcı')}"; + + static String m7(versionValue) => "Sürüm: ${versionValue}"; + + static String m8(freeAmount, storageUnit) => + "${freeAmount} ${storageUnit} free"; + + static String m9(paymentProvider) => + "Lütfen önce mevcut aboneliğinizi ${paymentProvider} adresinden iptal edin"; + + static String m10(user) => + "${user}, bu albüme daha fazla fotoğraf ekleyemeyecek.\n\nAncak, kendi eklediği mevcut fotoğrafları kaldırmaya devam edebilecektir"; + + static String m11(isFamilyMember, storageAmountInGb) => + "${Intl.select(isFamilyMember, { + 'true': 'Şu ana kadar aileniz ${storageAmountInGb} GB aldı', + 'false': 'Şu ana kadar ${storageAmountInGb} GB aldınız', + 'other': 'Şu ana kadar ${storageAmountInGb} GB aldınız!', + })}"; + + static String m12(albumName) => + "${albumName} için ortak çalışma bağlantısı oluşturuldu"; + + static String m13(familyAdminEmail) => + "Aboneliğinizi yönetmek için lütfen ${familyAdminEmail} ile iletişime geçin"; + + static String m14(provider) => + "Lütfen ${provider} aboneliğinizi yönetmek için support@ente.io adresinden bizimle iletişime geçin."; + + static String m15(endpoint) => "${endpoint}\'e bağlanıldı"; + + static String m16(count) => + "${Intl.plural(count, one: 'Delete ${count} item', other: 'Delete ${count} items')}"; + + static String m17(currentlyDeleting, totalCount) => + "Siliniyor ${currentlyDeleting} / ${totalCount}"; + + static String m18(albumName) => + "Bu, \"${albumName}\"e erişim için olan genel bağlantıyı kaldıracaktır."; + + static String m19(supportEmail) => + "Lütfen kayıtlı e-posta adresinizden ${supportEmail} adresine bir e-posta gönderin"; + + static String m20(count, storageSaved) => + "You have cleaned up ${Intl.plural(count, one: '${count} duplicate file', other: '${count} duplicate files')}, saving (${storageSaved}!)"; + + static String m21(count, formattedSize) => + "${count} dosyalar, ${formattedSize} her biri"; + + static String m22(newEmail) => "E-posta ${newEmail} olarak değiştirildi"; + + static String m23(email) => + "${email} does not have an Ente account.\n\nSend them an invite to share photos."; + + static String m24(count, formattedNumber) => + "Bu cihazdaki ${Intl.plural(count, one: '1 file', other: '${formattedNumber} dosya')} güvenli bir şekilde yedeklendi"; + + static String m25(count, formattedNumber) => + "Bu albümdeki ${Intl.plural(count, one: '1 file', other: '${formattedNumber} dosya')} güvenli bir şekilde yedeklendi"; + + static String m26(storageAmountInGB) => + "Birisinin davet kodunuzu uygulayıp ücretli hesap açtığı her seferede ${storageAmountInGB} GB"; + + static String m27(endDate) => "Ücretsiz deneme ${endDate} sona erir"; + + static String m28(count) => + "You can still access ${Intl.plural(count, one: 'it', other: 'them')} on Ente as long as you have an active subscription"; + + static String m29(sizeInMBorGB) => "${sizeInMBorGB} yer açın"; + + static String m30(count, formattedSize) => + "${Intl.plural(count, one: 'Yer açmak için cihazdan silinebilir ${formattedSize}', other: 'Yer açmak için cihazdan silinebilir ${formattedSize}')}"; + + static String m31(currentlyProcessing, totalCount) => + "Siliniyor ${currentlyProcessing} / ${totalCount}"; + + static String m32(count) => + "${Intl.plural(count, one: '${count} öğe', other: '${count} öğeler')}"; + + static String m33(expiryTime) => + "Bu bağlantı ${expiryTime} dan sonra geçersiz olacaktır"; + + static String m34(count, formattedCount) => + "${Intl.plural(count, zero: 'anı yok', one: '${formattedCount} anı', other: '${formattedCount} anılar')}"; + + static String m35(count) => + "${Intl.plural(count, one: 'Öğeyi taşı', other: 'Öğeleri taşı')}"; + + static String m36(albumName) => "${albumName} adlı albüme başarıyla taşındı"; + + static String m37(name) => "Not ${name}?"; + + static String m38(passwordStrengthValue) => + "Şifrenin güçlülük seviyesi: ${passwordStrengthValue}"; + + static String m39(providerName) => + "Sizden ücret alındıysa lütfen ${providerName} destek ekibiyle görüşün"; + + static String m40(endDate) => + "Free trial valid till ${endDate}.\nYou can choose a paid plan afterwards."; + + static String m41(toEmail) => "Lütfen bize ${toEmail} adresinden ulaşın"; + + static String m42(toEmail) => + "Lütfen günlükleri şu adrese gönderin\n${toEmail}"; + + static String m43(storeName) => "Bizi ${storeName} üzerinden değerlendirin"; + + static String m44(storageInGB) => "3. Hepimiz ${storageInGB} GB* bedava alın"; + + static String m45(userEmail) => + "${userEmail} bu paylaşılan albümden kaldırılacaktır\n\nOnlar tarafından eklenen tüm fotoğraflar da albümden kaldırılacaktır"; + + static String m46(endDate) => "Abonelik ${endDate} tarihinde yenilenir"; + + static String m47(count) => + "${Intl.plural(count, one: '${count} yıl önce', other: '${count} yıl önce')}"; + + static String m48(count) => "${count} seçildi"; + + static String m49(count, yourCount) => + "Seçilenler: ${count} (${yourCount} sizin seçiminiz)"; + + static String m50(verificationID) => + "İşte ente.io için doğrulama kimliğim: ${verificationID}."; + + static String m51(verificationID) => + "Merhaba, bu ente.io doğrulama kimliğinizin doğruluğunu onaylayabilir misiniz: ${verificationID}"; + + static String m52(referralCode, referralStorageInGB) => + "Ente referral code: ${referralCode} \n\nApply it in Settings → General → Referrals to get ${referralStorageInGB} GB free after you signup for a paid plan\n\nhttps://ente.io"; + + static String m53(numberOfPeople) => + "${Intl.plural(numberOfPeople, zero: 'Belirli kişilerle paylaş', one: '1 kişiyle paylaşıldı', other: '${numberOfPeople} kişiyle paylaşıldı')}"; + + static String m54(emailIDs) => "${emailIDs} ile paylaşıldı"; + + static String m55(fileType) => "Bu ${fileType}, cihazınızdan silinecek."; + + static String m56(fileType) => + "This ${fileType} is in both Ente and your device."; + + static String m57(fileType) => "This ${fileType} will be deleted from Ente."; + + static String m58(storageAmountInGB) => "${storageAmountInGB} GB"; + + static String m59( + usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) => + "${usedAmount} ${usedStorageUnit} / ${totalAmount} ${totalStorageUnit} kullanıldı"; + + static String m60(id) => + "Your ${id} is already linked to another Ente account.\nIf you would like to use your ${id} with this account, please contact our support\'\'"; + + static String m61(endDate) => + "Aboneliğiniz ${endDate} tarihinde iptal edilecektir"; + + static String m62(completed, total) => "${completed}/${total} anı korundu"; + + static String m63(storageAmountInGB) => + "Aynı zamanda ${storageAmountInGB} GB alıyorlar"; + + static String m64(email) => "Bu, ${email}\'in Doğrulama Kimliği"; + + static String m65(count) => + "${Intl.plural(count, zero: 'gün', one: '1 gün', other: '${count} gün')}"; + + static String m66(endDate) => "${endDate} tarihine kadar geçerli"; + + static String m67(email) => "${email} doğrula"; + + static String m68(email) => + "E-postayı ${email} adresine gönderdik"; + + static String m69(count) => + "${Intl.plural(count, one: '${count} yıl önce', other: '${count} yıl önce')}"; + + static String m70(storageSaved) => + "Başarılı bir şekilde ${storageSaved} alanını boşalttınız!"; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "aNewVersionOfEnteIsAvailable": MessageLookupByLibrary.simpleMessage( + "A new version of Ente is available."), + "about": MessageLookupByLibrary.simpleMessage("Hakkında"), + "account": MessageLookupByLibrary.simpleMessage("Hesap"), + "accountWelcomeBack": + MessageLookupByLibrary.simpleMessage("Tekrar hoş geldiniz!"), + "ackPasswordLostWarning": MessageLookupByLibrary.simpleMessage( + "Şifremi kaybedersem, verilerim uçtan uca şifrelendiği için verilerimi kaybedebileceğimi farkındayım."), + "activeSessions": + MessageLookupByLibrary.simpleMessage("Aktif oturumlar"), + "addAName": MessageLookupByLibrary.simpleMessage("Add a name"), + "addANewEmail": + MessageLookupByLibrary.simpleMessage("Yeni e-posta ekle"), + "addCollaborator": + MessageLookupByLibrary.simpleMessage("Düzenleyici ekle"), + "addCollaborators": m0, + "addFromDevice": MessageLookupByLibrary.simpleMessage("Cihazdan ekle"), + "addItem": m2, + "addLocation": MessageLookupByLibrary.simpleMessage("Konum Ekle"), + "addLocationButton": MessageLookupByLibrary.simpleMessage("Ekle"), + "addMore": MessageLookupByLibrary.simpleMessage("Daha fazla ekle"), + "addNew": MessageLookupByLibrary.simpleMessage("Yeni ekle"), + "addOnPageSubtitle": + MessageLookupByLibrary.simpleMessage("Eklentilerin ayrıntıları"), + "addOnValidTill": m3, + "addOns": MessageLookupByLibrary.simpleMessage("Eklentiler"), + "addPhotos": MessageLookupByLibrary.simpleMessage("Fotoğraf ekle"), + "addSelected": MessageLookupByLibrary.simpleMessage("Seçileni ekle"), + "addToAlbum": MessageLookupByLibrary.simpleMessage("Albüme ekle"), + "addToEnte": MessageLookupByLibrary.simpleMessage("Add to Ente"), + "addToHiddenAlbum": + MessageLookupByLibrary.simpleMessage("Gizli albüme ekle"), + "addViewer": MessageLookupByLibrary.simpleMessage("Görüntüleyici ekle"), + "addViewers": m1, + "addYourPhotosNow": MessageLookupByLibrary.simpleMessage( + "Fotoğraflarınızı şimdi ekleyin"), + "addedAs": MessageLookupByLibrary.simpleMessage("Eklendi"), + "addedBy": m4, + "addedSuccessfullyTo": m5, + "addingToFavorites": + MessageLookupByLibrary.simpleMessage("Favorilere ekleniyor..."), + "advanced": MessageLookupByLibrary.simpleMessage("Gelişmiş"), + "advancedSettings": MessageLookupByLibrary.simpleMessage("Gelişmiş"), + "after1Day": MessageLookupByLibrary.simpleMessage("1 gün sonra"), + "after1Hour": MessageLookupByLibrary.simpleMessage("1 saat sonra"), + "after1Month": MessageLookupByLibrary.simpleMessage("1 ay sonra"), + "after1Week": MessageLookupByLibrary.simpleMessage("1 hafta sonra"), + "after1Year": MessageLookupByLibrary.simpleMessage("1 yıl sonra"), + "albumOwner": MessageLookupByLibrary.simpleMessage("Sahip"), + "albumParticipantsCount": m6, + "albumTitle": MessageLookupByLibrary.simpleMessage("Albüm Başlığı"), + "albumUpdated": + MessageLookupByLibrary.simpleMessage("Albüm güncellendi"), + "albums": MessageLookupByLibrary.simpleMessage("Albümler"), + "allClear": MessageLookupByLibrary.simpleMessage("✨ Tamamen temizle"), + "allMemoriesPreserved": + MessageLookupByLibrary.simpleMessage("Tüm anılar saklandı"), + "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( + "Bağlantıya sahip olan kişilere, paylaşılan albüme fotoğraf eklemelerine izin ver."), + "allowAddingPhotos": + MessageLookupByLibrary.simpleMessage("Fotoğraf eklemeye izin ver"), + "allowDownloads": + MessageLookupByLibrary.simpleMessage("İndirmeye izin ver"), + "allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage( + "Kullanıcıların fotoğraf eklemesine izin ver"), + "androidBiometricHint": + MessageLookupByLibrary.simpleMessage("Kimliği doğrula"), + "androidBiometricNotRecognized": + MessageLookupByLibrary.simpleMessage("Tanınmadı. Tekrar deneyin."), + "androidBiometricRequiredTitle": + MessageLookupByLibrary.simpleMessage("Biyometrik gerekli"), + "androidBiometricSuccess": + MessageLookupByLibrary.simpleMessage("Başarılı"), + "androidCancelButton": MessageLookupByLibrary.simpleMessage("İptal et"), + "androidDeviceCredentialsRequiredTitle": + MessageLookupByLibrary.simpleMessage( + "Cihaz kimlik bilgileri gerekli"), + "androidDeviceCredentialsSetupDescription": + MessageLookupByLibrary.simpleMessage( + "Cihaz kimlik bilgileri gerekmekte"), + "androidGoToSettingsDescription": MessageLookupByLibrary.simpleMessage( + "Biyometrik kimlik doğrulama cihazınızda ayarlanmamış. Biyometrik kimlik doğrulama eklemek için \'Ayarlar > Güvenlik\' bölümüne gidin."), + "androidIosWebDesktop": + MessageLookupByLibrary.simpleMessage("Android, iOS, Web, Masaüstü"), + "androidSignInTitle": + MessageLookupByLibrary.simpleMessage("Kimlik doğrulaması gerekli"), + "appLock": MessageLookupByLibrary.simpleMessage("App lock"), + "appVersion": m7, + "appleId": MessageLookupByLibrary.simpleMessage("Apple kimliği"), + "apply": MessageLookupByLibrary.simpleMessage("Uygula"), + "applyCodeTitle": MessageLookupByLibrary.simpleMessage("Kodu girin"), + "appstoreSubscription": + MessageLookupByLibrary.simpleMessage("PlayStore aboneliği"), + "archive": MessageLookupByLibrary.simpleMessage("Arşiv"), + "archiveAlbum": MessageLookupByLibrary.simpleMessage("Albümü arşivle"), + "archiving": MessageLookupByLibrary.simpleMessage("Arşivleniyor..."), + "areYouSureThatYouWantToLeaveTheFamily": + MessageLookupByLibrary.simpleMessage( + "Aile planından ayrılmak istediğinize emin misiniz?"), + "areYouSureYouWantToCancel": MessageLookupByLibrary.simpleMessage( + "İptal etmek istediğinize emin misiniz?"), + "areYouSureYouWantToChangeYourPlan": + MessageLookupByLibrary.simpleMessage( + "Planı değistirmek istediğinize emin misiniz?"), + "areYouSureYouWantToExit": MessageLookupByLibrary.simpleMessage( + "Çıkmak istediğinden emin misin?"), + "areYouSureYouWantToLogout": MessageLookupByLibrary.simpleMessage( + "Çıkış yapmak istediğinize emin misiniz?"), + "areYouSureYouWantToRenew": MessageLookupByLibrary.simpleMessage( + "Yenilemek istediğinize emin misiniz?"), + "askCancelReason": MessageLookupByLibrary.simpleMessage( + "Aboneliğiniz iptal edilmiştir. Bunun sebebini paylaşmak ister misiniz?"), + "askDeleteReason": MessageLookupByLibrary.simpleMessage( + "Hesabınızı neden silmek istiyorsunuz?"), + "askYourLovedOnesToShare": MessageLookupByLibrary.simpleMessage( + "Sevdiklerinizden paylaşmalarını isteyin"), + "atAFalloutShelter": + MessageLookupByLibrary.simpleMessage("serpinti sığınağında"), + "authToChangeEmailVerificationSetting": + MessageLookupByLibrary.simpleMessage( + "E-posta doğrulamasını değiştirmek için lütfen kimlik doğrulaması yapın"), + "authToChangeLockscreenSetting": MessageLookupByLibrary.simpleMessage( + "Kilit ekranı ayarını değiştirmek için lütfen kimliğinizi doğrulayın"), + "authToChangeYourEmail": MessageLookupByLibrary.simpleMessage( + "E-postanızı değiştirmek için lütfen kimlik doğrulaması yapın"), + "authToChangeYourPassword": MessageLookupByLibrary.simpleMessage( + "Şifrenizi değiştirmek için lütfen kimlik doğrulaması yapın"), + "authToConfigureTwofactorAuthentication": + MessageLookupByLibrary.simpleMessage( + "İki faktörlü kimlik doğrulamayı yapılandırmak için lütfen kimlik doğrulaması yapın"), + "authToInitiateAccountDeletion": MessageLookupByLibrary.simpleMessage( + "Hesap silme işlemini başlatmak için lütfen kimlik doğrulaması yapın"), + "authToViewYourActiveSessions": MessageLookupByLibrary.simpleMessage( + "Aktif oturumlarınızı görüntülemek için lütfen kimliğinizi doğrulayın"), + "authToViewYourHiddenFiles": MessageLookupByLibrary.simpleMessage( + "Gizli dosyalarınızı görüntülemek için kimlik doğrulama yapınız"), + "authToViewYourMemories": MessageLookupByLibrary.simpleMessage( + "Kodlarınızı görmek için lütfen kimlik doğrulaması yapın"), + "authToViewYourRecoveryKey": MessageLookupByLibrary.simpleMessage( + "Kurtarma anahtarınızı görmek için lütfen kimliğinizi doğrulayın"), + "authenticating": + MessageLookupByLibrary.simpleMessage("Kimlik doğrulanıyor..."), + "authenticationFailedPleaseTryAgain": + MessageLookupByLibrary.simpleMessage( + "Kimlik doğrulama başarısız oldu, lütfen tekrar deneyin"), + "authenticationSuccessful": + MessageLookupByLibrary.simpleMessage("Kimlik doğrulama başarılı!"), + "autoCastDialogBody": MessageLookupByLibrary.simpleMessage( + "You\'ll see available Cast devices here."), + "autoCastiOSPermission": MessageLookupByLibrary.simpleMessage( + "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings."), + "autoLogoutMessage": MessageLookupByLibrary.simpleMessage( + "Due to technical glitch, you have been logged out. Our apologies for the inconvenience."), + "autoPair": MessageLookupByLibrary.simpleMessage("Auto pair"), + "autoPairDesc": MessageLookupByLibrary.simpleMessage( + "Auto pair works only with devices that support Chromecast."), + "available": MessageLookupByLibrary.simpleMessage("Mevcut"), + "availableStorageSpace": m8, + "backedUpFolders": + MessageLookupByLibrary.simpleMessage("Yedeklenmiş klasörler"), + "backup": MessageLookupByLibrary.simpleMessage("Yedekle"), + "backupFailed": + MessageLookupByLibrary.simpleMessage("Yedekleme başarısız oldu"), + "backupOverMobileData": + MessageLookupByLibrary.simpleMessage("Mobil veri ile yedekle"), + "backupSettings": + MessageLookupByLibrary.simpleMessage("Yedekleme seçenekleri"), + "backupVideos": + MessageLookupByLibrary.simpleMessage("Videolari yedekle"), + "blackFridaySale": + MessageLookupByLibrary.simpleMessage("Muhteşem Cuma kampanyası"), + "blog": MessageLookupByLibrary.simpleMessage("Blog"), + "cachedData": + MessageLookupByLibrary.simpleMessage("Ön belleğe alınan veri"), + "calculating": MessageLookupByLibrary.simpleMessage("Hesaplanıyor..."), + "canNotUploadToAlbumsOwnedByOthers": + MessageLookupByLibrary.simpleMessage( + "Başkalarına ait albümlere yüklenemez"), + "canOnlyCreateLinkForFilesOwnedByYou": + MessageLookupByLibrary.simpleMessage( + "Yalnızca size ait dosyalar için bağlantı oluşturabilir"), + "canOnlyRemoveFilesOwnedByYou": MessageLookupByLibrary.simpleMessage( + "Yalnızca size ait dosyaları kaldırabilir"), + "cancel": MessageLookupByLibrary.simpleMessage("İptal Et"), + "cancelOtherSubscription": m9, + "cancelSubscription": + MessageLookupByLibrary.simpleMessage("Abonelik iptali"), + "cannotAddMorePhotosAfterBecomingViewer": m10, + "cannotDeleteSharedFiles": + MessageLookupByLibrary.simpleMessage("Dosyalar silinemiyor"), + "castIPMismatchBody": MessageLookupByLibrary.simpleMessage( + "Please make sure you are on the same network as the TV."), + "castIPMismatchTitle": + MessageLookupByLibrary.simpleMessage("Failed to cast album"), + "castInstruction": MessageLookupByLibrary.simpleMessage( + "Eşleştirmek istediğiniz cihazda cast.ente.io adresini ziyaret edin.\n\nAlbümü TV\'nizde oynatmak için aşağıdaki kodu girin."), + "centerPoint": MessageLookupByLibrary.simpleMessage("Merkez noktası"), + "changeEmail": + MessageLookupByLibrary.simpleMessage("E-posta adresini değiştir"), + "changeLocationOfSelectedItems": MessageLookupByLibrary.simpleMessage( + "Seçilen öğelerin konumu değiştirilsin mi?"), + "changePassword": + MessageLookupByLibrary.simpleMessage("Sifrenizi değiştirin"), + "changePasswordTitle": + MessageLookupByLibrary.simpleMessage("Parolanızı değiştirin"), + "changePermissions": + MessageLookupByLibrary.simpleMessage("İzinleri değiştir?"), + "checkForUpdates": + MessageLookupByLibrary.simpleMessage("Güncellemeleri kontol et"), + "checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage( + "Lütfen doğrulama işlemini tamamlamak için gelen kutunuzu (ve spam klasörünüzü) kontrol edin"), + "checkStatus": MessageLookupByLibrary.simpleMessage("Check status"), + "checking": MessageLookupByLibrary.simpleMessage("Kontrol ediliyor..."), + "claimFreeStorage": + MessageLookupByLibrary.simpleMessage("Bedava alan talep edin"), + "claimMore": MessageLookupByLibrary.simpleMessage("Arttır!"), + "claimed": MessageLookupByLibrary.simpleMessage("Alındı"), + "claimedStorageSoFar": m11, + "cleanUncategorized": + MessageLookupByLibrary.simpleMessage("Temiz Genel"), + "cleanUncategorizedDescription": MessageLookupByLibrary.simpleMessage( + "Diğer albümlerde bulunan Kategorilenmemiş tüm dosyaları kaldırın"), + "clearCaches": + MessageLookupByLibrary.simpleMessage("Önbellekleri temizle"), + "clearIndexes": MessageLookupByLibrary.simpleMessage("Açık Dizin"), + "click": MessageLookupByLibrary.simpleMessage("• Tıklamak"), + "clickOnTheOverflowMenu": + MessageLookupByLibrary.simpleMessage("• Taşma menüsüne tıklayın"), + "close": MessageLookupByLibrary.simpleMessage("Kapat"), + "clubByCaptureTime": MessageLookupByLibrary.simpleMessage( + "Yakalama zamanına göre kulüp"), + "clubByFileName": + MessageLookupByLibrary.simpleMessage("Dosya adına göre kulüp"), + "clusteringProgress": + MessageLookupByLibrary.simpleMessage("Clustering progress"), + "codeAppliedPageTitle": + MessageLookupByLibrary.simpleMessage("Kod kabul edildi"), + "codeCopiedToClipboard": + MessageLookupByLibrary.simpleMessage("Kodunuz panoya kopyalandı"), + "codeUsedByYou": + MessageLookupByLibrary.simpleMessage("Sizin kullandığınız kod"), + "collabLinkSectionDescription": MessageLookupByLibrary.simpleMessage( + "Create a link to allow people to add and view photos in your shared album without needing an Ente app or account. Great for collecting event photos."), + "collaborativeLink": + MessageLookupByLibrary.simpleMessage("Organizasyon bağlantısı"), + "collaborativeLinkCreatedFor": m12, + "collaborator": MessageLookupByLibrary.simpleMessage("Düzenleyici"), + "collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": + MessageLookupByLibrary.simpleMessage( + "Düzenleyiciler, paylaşılan albüme fotoğraf ve videolar ekleyebilir."), + "collageLayout": MessageLookupByLibrary.simpleMessage("Düzen"), + "collageSaved": MessageLookupByLibrary.simpleMessage( + "Kolajınız galeriye kaydedildi"), + "collectEventPhotos": MessageLookupByLibrary.simpleMessage( + "Etkinlik fotoğraflarını topla"), + "collectPhotos": + MessageLookupByLibrary.simpleMessage("Fotoğrafları topla"), + "color": MessageLookupByLibrary.simpleMessage("Renk"), + "confirm": MessageLookupByLibrary.simpleMessage("Onayla"), + "confirm2FADisable": MessageLookupByLibrary.simpleMessage( + "İki adımlı kimlik doğrulamasını devre dışı bırakmak istediğinize emin misiniz?"), + "confirmAccountDeletion": + MessageLookupByLibrary.simpleMessage("Hesap silme işlemini onayla"), + "confirmDeletePrompt": MessageLookupByLibrary.simpleMessage( + "Evet, bu hesabı ve tüm verileri kalıcı olarak silmek istiyorum."), + "confirmPassword": + MessageLookupByLibrary.simpleMessage("Şifrenizi onaylayın"), + "confirmPlanChange": MessageLookupByLibrary.simpleMessage( + "Plan değişikliğini onaylayın"), + "confirmRecoveryKey": + MessageLookupByLibrary.simpleMessage("Kurtarma anahtarını doğrula"), + "confirmYourRecoveryKey": MessageLookupByLibrary.simpleMessage( + "Kurtarma anahtarını doğrulayın"), + "connectToDevice": + MessageLookupByLibrary.simpleMessage("Connect to device"), + "contactFamilyAdmin": m13, + "contactSupport": + MessageLookupByLibrary.simpleMessage("Destek ile iletişim"), + "contactToManageSubscription": m14, + "contacts": MessageLookupByLibrary.simpleMessage("Kişiler"), + "contents": MessageLookupByLibrary.simpleMessage("İçerikler"), + "continueLabel": MessageLookupByLibrary.simpleMessage("Devam edin"), + "continueOnFreeTrial": + MessageLookupByLibrary.simpleMessage("Ücretsiz denemeye devam et"), + "convertToAlbum": MessageLookupByLibrary.simpleMessage("Albüme taşı"), + "copyEmailAddress": + MessageLookupByLibrary.simpleMessage("E-posta adresini kopyala"), + "copyLink": MessageLookupByLibrary.simpleMessage("Linki kopyala"), + "copypasteThisCodentoYourAuthenticatorApp": + MessageLookupByLibrary.simpleMessage( + "Bu kodu kopyalayın ve kimlik doğrulama uygulamanıza yapıştırın"), + "couldNotBackUpTryLater": MessageLookupByLibrary.simpleMessage( + "Verilerinizi yedekleyemedik.\nDaha sonra tekrar deneyeceğiz."), + "couldNotFreeUpSpace": + MessageLookupByLibrary.simpleMessage("Yer boşaltılamadı"), + "couldNotUpdateSubscription": + MessageLookupByLibrary.simpleMessage("Abonelikler kaydedilemedi"), + "count": MessageLookupByLibrary.simpleMessage("Miktar"), + "crashReporting": + MessageLookupByLibrary.simpleMessage("Çökme raporlaması"), + "create": MessageLookupByLibrary.simpleMessage("Oluştur"), + "createAccount": + MessageLookupByLibrary.simpleMessage("Hesap oluşturun"), + "createAlbumActionHint": MessageLookupByLibrary.simpleMessage( + "Fotoğrafları seçmek için uzun basın ve + düğmesine tıklayarak bir albüm oluşturun"), + "createCollaborativeLink": + MessageLookupByLibrary.simpleMessage("Create collaborative link"), + "createCollage": MessageLookupByLibrary.simpleMessage("Kolaj oluştur"), + "createNewAccount": + MessageLookupByLibrary.simpleMessage("Yeni bir hesap oluşturun"), + "createOrSelectAlbum": + MessageLookupByLibrary.simpleMessage("Albüm oluştur veya seç"), + "createPublicLink": + MessageLookupByLibrary.simpleMessage("Herkese açık link oluştur"), + "creatingLink": + MessageLookupByLibrary.simpleMessage("Bağlantı oluşturuluyor..."), + "criticalUpdateAvailable": + MessageLookupByLibrary.simpleMessage("Kritik güncelleme mevcut"), + "crop": MessageLookupByLibrary.simpleMessage("Crop"), + "currentUsageIs": + MessageLookupByLibrary.simpleMessage("Güncel kullanımınız "), + "custom": MessageLookupByLibrary.simpleMessage("Kişisel"), + "customEndpoint": m15, + "darkTheme": MessageLookupByLibrary.simpleMessage("Karanlık"), + "dayToday": MessageLookupByLibrary.simpleMessage("Bugün"), + "dayYesterday": MessageLookupByLibrary.simpleMessage("Dün"), + "decrypting": + MessageLookupByLibrary.simpleMessage("Şifre çözülüyor..."), + "decryptingVideo": MessageLookupByLibrary.simpleMessage( + "Videonun şifresi çözülüyor..."), + "deduplicateFiles": + MessageLookupByLibrary.simpleMessage("Dosyaları Tekilleştirme"), + "delete": MessageLookupByLibrary.simpleMessage("Sil"), + "deleteAccount": MessageLookupByLibrary.simpleMessage("Hesabı sil"), + "deleteAccountFeedbackPrompt": MessageLookupByLibrary.simpleMessage( + "Aramızdan ayrıldığınız için üzgünüz. Lütfen kendimizi geliştirmemize yardımcı olun. Neden ayrıldığınızı Açıklar mısınız."), + "deleteAccountPermanentlyButton": + MessageLookupByLibrary.simpleMessage("Hesabımı kalıcı olarak sil"), + "deleteAlbum": MessageLookupByLibrary.simpleMessage("Albümü sil"), + "deleteAlbumDialog": MessageLookupByLibrary.simpleMessage( + "Ayrıca bu albümde bulunan fotoğrafları (ve videoları) parçası oldukları tüm diğer albümlerden silebilir miyim?"), + "deleteAlbumsDialogBody": MessageLookupByLibrary.simpleMessage( + "Bu, tüm boş albümleri silecektir. Bu, albüm listenizdeki dağınıklığı azaltmak istediğinizde kullanışlıdır."), + "deleteAll": MessageLookupByLibrary.simpleMessage("Hepsini Sil"), + "deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage( + "This account is linked to other Ente apps, if you use any. Your uploaded data, across all Ente apps, will be scheduled for deletion, and your account will be permanently deleted."), + "deleteEmailRequest": MessageLookupByLibrary.simpleMessage( + "Lütfen kayıtlı e-posta adresinizden account-deletion@ente.io\'a e-posta gönderiniz."), + "deleteEmptyAlbums": + MessageLookupByLibrary.simpleMessage("Boş albümleri sil"), + "deleteEmptyAlbumsWithQuestionMark": + MessageLookupByLibrary.simpleMessage("Boş albümleri sileyim mi?"), + "deleteFromBoth": + MessageLookupByLibrary.simpleMessage("Her ikisinden de sil"), + "deleteFromDevice": + MessageLookupByLibrary.simpleMessage("Cihazınızdan silin"), + "deleteFromEnte": + MessageLookupByLibrary.simpleMessage("Delete from Ente"), + "deleteItemCount": m16, + "deleteLocation": MessageLookupByLibrary.simpleMessage("Konumu sil"), + "deletePhotos": + MessageLookupByLibrary.simpleMessage("Fotoğrafları sil"), + "deleteProgress": m17, + "deleteReason1": MessageLookupByLibrary.simpleMessage( + "İhtiyacım olan önemli bir özellik eksik"), + "deleteReason2": MessageLookupByLibrary.simpleMessage( + "Uygulama veya bir özellik olması gerektiğini düşündüğüm gibi çalışmıyor"), + "deleteReason3": MessageLookupByLibrary.simpleMessage( + "Daha çok sevdiğim başka bir hizmet buldum"), + "deleteReason4": + MessageLookupByLibrary.simpleMessage("Nedenim listede yok"), + "deleteRequestSLAText": MessageLookupByLibrary.simpleMessage( + "İsteğiniz 72 saat içinde gerçekleştirilecek."), + "deleteSharedAlbum": MessageLookupByLibrary.simpleMessage( + "Paylaşılan albüm silinsin mi?"), + "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage( + "Albüm herkes için silinecek\n\nBu albümdeki başkalarına ait paylaşılan fotoğraflara erişiminizi kaybedeceksiniz"), + "descriptions": MessageLookupByLibrary.simpleMessage("Açıklama"), + "deselectAll": + MessageLookupByLibrary.simpleMessage("Tüm seçimi kaldır"), + "designedToOutlive": MessageLookupByLibrary.simpleMessage( + "Hayatta kalmak için tasarlandı"), + "details": MessageLookupByLibrary.simpleMessage("Ayrıntılar"), + "developerSettings": + MessageLookupByLibrary.simpleMessage("Geliştirici ayarları"), + "developerSettingsWarning": MessageLookupByLibrary.simpleMessage( + "Geliştirici ayarlarını değiştirmek istediğinizden emin misiniz?"), + "deviceCodeHint": MessageLookupByLibrary.simpleMessage("Kodu girin"), + "deviceFilesAutoUploading": MessageLookupByLibrary.simpleMessage( + "Files added to this device album will automatically get uploaded to Ente."), + "deviceLock": MessageLookupByLibrary.simpleMessage("Device lock"), + "deviceLockExplanation": MessageLookupByLibrary.simpleMessage( + "Disable the device screen lock when Ente is in the foreground and there is a backup in progress. This is normally not needed, but may help big uploads and initial imports of large libraries complete faster."), + "deviceNotFound": + MessageLookupByLibrary.simpleMessage("Cihaz bulunamadı"), + "didYouKnow": MessageLookupByLibrary.simpleMessage("Biliyor musun?"), + "disableAutoLock": MessageLookupByLibrary.simpleMessage( + "Otomatik kilidi devre dışı bırak"), + "disableDownloadWarningBody": MessageLookupByLibrary.simpleMessage( + "Görüntüleyiciler, hala harici araçlar kullanarak ekran görüntüsü alabilir veya fotoğraflarınızın bir kopyasını kaydedebilir. Lütfen bunu göz önünde bulundurunuz"), + "disableDownloadWarningTitle": + MessageLookupByLibrary.simpleMessage("Lütfen dikkate alın"), + "disableLinkMessage": m18, + "disableTwofactor": MessageLookupByLibrary.simpleMessage( + "İki Aşamalı Doğrulamayı Devre Dışı Bırak"), + "disablingTwofactorAuthentication": + MessageLookupByLibrary.simpleMessage( + "İki aşamalı doğrulamayı devre dışı bırak..."), + "discord": MessageLookupByLibrary.simpleMessage("Discord"), + "dismiss": MessageLookupByLibrary.simpleMessage("Reddet"), + "distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"), + "doNotSignOut": MessageLookupByLibrary.simpleMessage("Çıkış yapma"), + "doThisLater": MessageLookupByLibrary.simpleMessage("Sonra yap"), + "doYouWantToDiscardTheEditsYouHaveMade": + MessageLookupByLibrary.simpleMessage( + "Yaptığınız düzenlemeleri silmek istiyor musunuz?"), + "done": MessageLookupByLibrary.simpleMessage("Bitti"), + "doubleYourStorage": MessageLookupByLibrary.simpleMessage( + "Depolama alanınızı ikiye katlayın"), + "download": MessageLookupByLibrary.simpleMessage("İndir"), + "downloadFailed": + MessageLookupByLibrary.simpleMessage("İndirme başarısız"), + "downloading": MessageLookupByLibrary.simpleMessage("İndiriliyor..."), + "dropSupportEmail": m19, + "duplicateFileCountWithStorageSaved": m20, + "duplicateItemsGroup": m21, + "edit": MessageLookupByLibrary.simpleMessage("Düzenle"), + "editLocation": MessageLookupByLibrary.simpleMessage("Konumu düzenle"), + "editLocationTagTitle": + MessageLookupByLibrary.simpleMessage("Konumu düzenle"), + "editsSaved": + MessageLookupByLibrary.simpleMessage("Düzenleme kaydedildi"), + "editsToLocationWillOnlyBeSeenWithinEnte": + MessageLookupByLibrary.simpleMessage( + "Konumda yapılan düzenlemeler yalnızca Ente\'de görülecektir"), + "eligible": MessageLookupByLibrary.simpleMessage("uygun"), + "email": MessageLookupByLibrary.simpleMessage("E-Posta"), + "emailChangedTo": m22, + "emailNoEnteAccount": m23, + "emailVerificationToggle": + MessageLookupByLibrary.simpleMessage("E-posta doğrulama"), + "emailYourLogs": MessageLookupByLibrary.simpleMessage( + "Günlüklerinizi e-postayla gönderin"), + "empty": MessageLookupByLibrary.simpleMessage("Boşalt"), + "emptyTrash": + MessageLookupByLibrary.simpleMessage("Çöp kutusu boşaltılsın mı?"), + "enableMaps": + MessageLookupByLibrary.simpleMessage("Haritaları Etkinleştir"), + "enableMapsDesc": MessageLookupByLibrary.simpleMessage( + "Bu, fotoğraflarınızı bir dünya haritasında gösterecektir.\n\nBu harita Open Street Map tarafından barındırılmaktadır ve fotoğraflarınızın tam konumları hiçbir zaman paylaşılmaz.\n\nBu özelliği istediğiniz zaman Ayarlar\'dan devre dışı bırakabilirsiniz."), + "encryptingBackup": + MessageLookupByLibrary.simpleMessage("Yedekleme şifreleniyor..."), + "encryption": MessageLookupByLibrary.simpleMessage("Şifreleme"), + "encryptionKeys": + MessageLookupByLibrary.simpleMessage("Sifreleme anahtarı"), + "endpointUpdatedMessage": MessageLookupByLibrary.simpleMessage( + "Fatura başarıyla güncellendi"), + "endtoendEncryptedByDefault": MessageLookupByLibrary.simpleMessage( + "Varsayılan olarak uçtan uca şifrelenmiş"), + "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": + MessageLookupByLibrary.simpleMessage( + "Ente can encrypt and preserve files only if you grant access to them"), + "entePhotosPerm": MessageLookupByLibrary.simpleMessage( + "Ente needs permission to preserve your photos"), + "enteSubscriptionPitch": MessageLookupByLibrary.simpleMessage( + "Ente preserves your memories, so they\'re always available to you, even if you lose your device."), + "enteSubscriptionShareWithFamily": MessageLookupByLibrary.simpleMessage( + "Aileniz de planınıza eklenebilir."), + "enterAlbumName": + MessageLookupByLibrary.simpleMessage("Bir albüm adı girin"), + "enterCode": MessageLookupByLibrary.simpleMessage("Kodu giriniz"), + "enterCodeDescription": MessageLookupByLibrary.simpleMessage( + "Arkadaşınız tarafından sağlanan kodu girerek hem sizin hem de arkadaşınızın ücretsiz depolamayı talep etmek için girin"), + "enterEmail": + MessageLookupByLibrary.simpleMessage("E-postanızı giriniz"), + "enterFileName": + MessageLookupByLibrary.simpleMessage("Dosya adını girin"), + "enterNewPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( + "Verilerinizi şifrelemek için kullanabileceğimiz yeni bir şifre girin"), + "enterPassword": + MessageLookupByLibrary.simpleMessage("Şifrenizi girin"), + "enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( + "Verilerinizi şifrelemek için kullanabileceğimiz bir şifre girin"), + "enterPersonName": + MessageLookupByLibrary.simpleMessage("Enter person name"), + "enterPin": MessageLookupByLibrary.simpleMessage("Enter PIN"), + "enterReferralCode": + MessageLookupByLibrary.simpleMessage("Davet kodunuzu girin"), + "enterThe6digitCodeFromnyourAuthenticatorApp": + MessageLookupByLibrary.simpleMessage( + "Doğrulama uygulamasındaki 6 basamaklı kodu giriniz"), + "enterValidEmail": MessageLookupByLibrary.simpleMessage( + "Lütfen geçerli bir E-posta adresi girin."), + "enterYourEmailAddress": + MessageLookupByLibrary.simpleMessage("E-posta adresinizi girin"), + "enterYourPassword": + MessageLookupByLibrary.simpleMessage("Lütfen şifrenizi giriniz"), + "enterYourRecoveryKey": + MessageLookupByLibrary.simpleMessage("Kurtarma kodunuzu girin"), + "error": MessageLookupByLibrary.simpleMessage("Hata"), + "everywhere": MessageLookupByLibrary.simpleMessage("her yerde"), + "exif": MessageLookupByLibrary.simpleMessage("EXIF"), + "existingUser": + MessageLookupByLibrary.simpleMessage("Mevcut kullanıcı"), + "expiredLinkInfo": MessageLookupByLibrary.simpleMessage( + "Bu bağlantının süresi dolmuştur. Lütfen yeni bir süre belirleyin veya bağlantı süresini devre dışı bırakın."), + "exportLogs": + MessageLookupByLibrary.simpleMessage("Günlüğü dışa aktar"), + "exportYourData": + MessageLookupByLibrary.simpleMessage("Veriyi dışarı aktar"), + "faceRecognition": + MessageLookupByLibrary.simpleMessage("Face recognition"), + "faces": MessageLookupByLibrary.simpleMessage("Yüzler"), + "failedToApplyCode": + MessageLookupByLibrary.simpleMessage("Uygulanırken hata oluştu"), + "failedToCancel": MessageLookupByLibrary.simpleMessage( + "İptal edilirken sorun oluştu"), + "failedToDownloadVideo": + MessageLookupByLibrary.simpleMessage("Video indirilemedi"), + "failedToFetchOriginalForEdit": MessageLookupByLibrary.simpleMessage( + "Düzenleme için orijinal getirilemedi"), + "failedToFetchReferralDetails": MessageLookupByLibrary.simpleMessage( + "Davet ayrıntıları çekilemedi. Iütfen daha sonra deneyin."), + "failedToLoadAlbums": MessageLookupByLibrary.simpleMessage( + "Albüm yüklenirken hata oluştu"), + "failedToRenew": MessageLookupByLibrary.simpleMessage( + "Abonelik yenilenirken hata oluştu"), + "failedToVerifyPaymentStatus": + MessageLookupByLibrary.simpleMessage("Ödeme durumu doğrulanamadı"), + "familyPlanOverview": MessageLookupByLibrary.simpleMessage( + "Add 5 family members to your existing plan without paying extra.\n\nEach member gets their own private space, and cannot see each other\'s files unless they\'re shared.\n\nFamily plans are available to customers who have a paid Ente subscription.\n\nSubscribe now to get started!"), + "familyPlanPortalTitle": MessageLookupByLibrary.simpleMessage("Aile"), + "familyPlans": MessageLookupByLibrary.simpleMessage("Aile Planı"), + "faq": MessageLookupByLibrary.simpleMessage("Sıkça sorulan sorular"), + "faqs": MessageLookupByLibrary.simpleMessage("Sık sorulanlar"), + "favorite": MessageLookupByLibrary.simpleMessage("Favori"), + "feedback": MessageLookupByLibrary.simpleMessage("Geri Bildirim"), + "fileFailedToSaveToGallery": MessageLookupByLibrary.simpleMessage( + "Dosya galeriye kaydedilemedi"), + "fileInfoAddDescHint": + MessageLookupByLibrary.simpleMessage("Bir açıklama ekle..."), + "fileSavedToGallery": + MessageLookupByLibrary.simpleMessage("Video galeriye kaydedildi"), + "fileTypes": MessageLookupByLibrary.simpleMessage("Dosya türü"), + "fileTypesAndNames": + MessageLookupByLibrary.simpleMessage("Dosya türleri ve adları"), + "filesBackedUpFromDevice": m24, + "filesBackedUpInAlbum": m25, + "filesDeleted": + MessageLookupByLibrary.simpleMessage("Dosyalar silinmiş"), + "filesSavedToGallery": + MessageLookupByLibrary.simpleMessage("Files saved to gallery"), + "findPeopleByName": + MessageLookupByLibrary.simpleMessage("Find people quickly by name"), + "flip": MessageLookupByLibrary.simpleMessage("Çevir"), + "forYourMemories": + MessageLookupByLibrary.simpleMessage("anıların için"), + "forgotPassword": + MessageLookupByLibrary.simpleMessage("Şifremi unuttum"), + "foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"), + "freeStorageClaimed": + MessageLookupByLibrary.simpleMessage("Alınan bedava alan"), + "freeStorageOnReferralSuccess": m26, + "freeStorageUsable": + MessageLookupByLibrary.simpleMessage("Kullanılabilir bedava alan"), + "freeTrial": MessageLookupByLibrary.simpleMessage("Ücretsiz deneme"), + "freeTrialValidTill": m27, + "freeUpAccessPostDelete": m28, + "freeUpAmount": m29, + "freeUpDeviceSpace": + MessageLookupByLibrary.simpleMessage("Cihaz alanını boşaltın"), + "freeUpDeviceSpaceDesc": MessageLookupByLibrary.simpleMessage( + "Save space on your device by clearing files that have been already backed up."), + "freeUpSpace": MessageLookupByLibrary.simpleMessage("Boş alan"), + "freeUpSpaceSaving": m30, + "galleryMemoryLimitInfo": MessageLookupByLibrary.simpleMessage( + "Galeride 1000\'e kadar anı gösterilir"), + "general": MessageLookupByLibrary.simpleMessage("Genel"), + "generatingEncryptionKeys": MessageLookupByLibrary.simpleMessage( + "Şifreleme anahtarı oluşturuluyor..."), + "genericProgress": m31, + "goToSettings": MessageLookupByLibrary.simpleMessage("Ayarlara git"), + "googlePlayId": + MessageLookupByLibrary.simpleMessage("Google play kimliği"), + "grantFullAccessPrompt": MessageLookupByLibrary.simpleMessage( + "Lütfen Ayarlar uygulamasında tüm fotoğraflara erişime izin verin"), + "grantPermission": + MessageLookupByLibrary.simpleMessage("İzinleri değiştir"), + "groupNearbyPhotos": MessageLookupByLibrary.simpleMessage( + "Yakındaki fotoğrafları gruplandır"), + "hearUsExplanation": MessageLookupByLibrary.simpleMessage( + "Biz uygulama kurulumlarını takip etmiyoruz. Bizi nereden duyduğunuzdan bahsetmeniz bize çok yardımcı olacak!"), + "hearUsWhereTitle": MessageLookupByLibrary.simpleMessage( + "Ente\'yi nereden duydunuz? (opsiyonel)"), + "help": MessageLookupByLibrary.simpleMessage("Yardım"), + "hidden": MessageLookupByLibrary.simpleMessage("Gizle"), + "hide": MessageLookupByLibrary.simpleMessage("Gizle"), + "hiding": MessageLookupByLibrary.simpleMessage("Gizleniyor..."), + "hostedAtOsmFrance": + MessageLookupByLibrary.simpleMessage("OSM Fransa\'da ağırlandı"), + "howItWorks": MessageLookupByLibrary.simpleMessage("Nasıl çalışır"), + "howToViewShareeVerificationID": MessageLookupByLibrary.simpleMessage( + "Lütfen onlardan ayarlar ekranında e-posta adresine uzun süre basmalarını ve her iki cihazdaki kimliklerin eşleştiğini doğrulamalarını isteyin."), + "iOSGoToSettingsDescription": MessageLookupByLibrary.simpleMessage( + "Cihazınızda biyometrik kimlik doğrulama ayarlanmamış. Lütfen telefonunuzda Touch ID veya Face ID\'yi etkinleştirin."), + "iOSLockOut": MessageLookupByLibrary.simpleMessage( + "Biyometrik kimlik doğrulama devre dışı. Etkinleştirmek için lütfen ekranınızı kilitleyin ve kilidini açın."), + "iOSOkButton": MessageLookupByLibrary.simpleMessage("Tamam"), + "ignoreUpdate": MessageLookupByLibrary.simpleMessage("Yoksay"), + "ignoredFolderUploadReason": MessageLookupByLibrary.simpleMessage( + "Some files in this album are ignored from upload because they had previously been deleted from Ente."), + "importing": + MessageLookupByLibrary.simpleMessage("İçeri aktarılıyor...."), + "incorrectCode": MessageLookupByLibrary.simpleMessage("Yanlış kod"), + "incorrectPasswordTitle": + MessageLookupByLibrary.simpleMessage("Yanlış şifre"), + "incorrectRecoveryKey": + MessageLookupByLibrary.simpleMessage("Yanlış kurtarma kodu"), + "incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage( + "Girdiğiniz kurtarma kod yanlış"), + "incorrectRecoveryKeyTitle": + MessageLookupByLibrary.simpleMessage("Yanlış kurtarma kodu"), + "indexedItems": + MessageLookupByLibrary.simpleMessage("Yeni öğeleri indeksle"), + "indexingIsPaused": MessageLookupByLibrary.simpleMessage( + "Indexing is paused. It will automatically resume when device is ready."), + "insecureDevice": + MessageLookupByLibrary.simpleMessage("Güvenilir olmayan cihaz"), + "installManually": + MessageLookupByLibrary.simpleMessage("Manuel kurulum"), + "invalidEmailAddress": + MessageLookupByLibrary.simpleMessage("Geçersiz e-posta adresi"), + "invalidEndpoint": + MessageLookupByLibrary.simpleMessage("Geçersiz uç nokta"), + "invalidEndpointMessage": MessageLookupByLibrary.simpleMessage( + "Üzgünüz, girdiğiniz uç nokta geçersiz. Lütfen geçerli bir uç nokta girin ve tekrar deneyin."), + "invalidKey": MessageLookupByLibrary.simpleMessage("Gecersiz anahtar"), + "invalidRecoveryKey": MessageLookupByLibrary.simpleMessage( + "Girdiğiniz kurtarma anahtarı geçerli değil. Lütfen anahtarın 24 kelime içerdiğinden ve her bir kelimenin doğru şekilde yazıldığından emin olun.\n\nEğer eski bir kurtarma kodu girdiyseniz, o zaman kodun 64 karakter uzunluğunda olduğunu kontrol edin."), + "invite": MessageLookupByLibrary.simpleMessage("Davet et"), + "inviteToEnte": MessageLookupByLibrary.simpleMessage("Invite to Ente"), + "inviteYourFriends": + MessageLookupByLibrary.simpleMessage("Arkadaşlarını davet et"), + "inviteYourFriendsToEnte": + MessageLookupByLibrary.simpleMessage("Invite your friends to Ente"), + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": + MessageLookupByLibrary.simpleMessage( + "Bir şeyler ters gitmiş gibi görünüyor. Lütfen bir süre sonra tekrar deneyin. Hata devam ederse, lütfen destek ekibimizle iletişime geçin."), + "itemCount": m32, + "itemsShowTheNumberOfDaysRemainingBeforePermanentDeletion": + MessageLookupByLibrary.simpleMessage( + "Öğeler, kalıcı olarak silinmeden önce kalan gün sayısını gösterir"), + "itemsWillBeRemovedFromAlbum": MessageLookupByLibrary.simpleMessage( + "Seçilen öğeler bu albümden kaldırılacak"), + "joinDiscord": MessageLookupByLibrary.simpleMessage("Discord\'a Katıl"), + "keepPhotos": + MessageLookupByLibrary.simpleMessage("Fotoğrafları sakla"), + "kiloMeterUnit": MessageLookupByLibrary.simpleMessage("km"), + "kindlyHelpUsWithThisInformation": MessageLookupByLibrary.simpleMessage( + "Lütfen bu bilgilerle bize yardımcı olun"), + "language": MessageLookupByLibrary.simpleMessage("Dil"), + "lastUpdated": + MessageLookupByLibrary.simpleMessage("En son güncellenen"), + "leave": MessageLookupByLibrary.simpleMessage("Çıkış yap"), + "leaveAlbum": + MessageLookupByLibrary.simpleMessage("Albümü yeniden adlandır"), + "leaveFamily": + MessageLookupByLibrary.simpleMessage("Aile planından ayrıl"), + "leaveSharedAlbum": MessageLookupByLibrary.simpleMessage( + "Paylaşılan albüm silinsin mi?"), + "left": MessageLookupByLibrary.simpleMessage("Left"), + "light": MessageLookupByLibrary.simpleMessage("Aydınlık"), + "lightTheme": MessageLookupByLibrary.simpleMessage("Aydınlık"), + "linkCopiedToClipboard": + MessageLookupByLibrary.simpleMessage("Link panoya kopyalandı"), + "linkDeviceLimit": MessageLookupByLibrary.simpleMessage("Cihaz limiti"), + "linkEnabled": MessageLookupByLibrary.simpleMessage("Geçerli"), + "linkExpired": MessageLookupByLibrary.simpleMessage("Süresi dolmuş"), + "linkExpiresOn": m33, + "linkExpiry": + MessageLookupByLibrary.simpleMessage("Linkin geçerliliği"), + "linkHasExpired": + MessageLookupByLibrary.simpleMessage("Bağlantının süresi dolmuş"), + "linkNeverExpires": MessageLookupByLibrary.simpleMessage("Asla"), + "livePhotos": MessageLookupByLibrary.simpleMessage("Canlı Fotoğraf"), + "loadMessage1": MessageLookupByLibrary.simpleMessage( + "Aboneliğinizi ailenizle paylaşabilirsiniz"), + "loadMessage2": MessageLookupByLibrary.simpleMessage( + "Şu ana kadar 30 milyondan fazla anıyı koruduk"), + "loadMessage3": MessageLookupByLibrary.simpleMessage( + "Verilerinizin 3 kopyasını saklıyoruz, biri yer altı serpinti sığınağında"), + "loadMessage4": MessageLookupByLibrary.simpleMessage( + "Tüm uygulamalarımız açık kaynaktır"), + "loadMessage5": MessageLookupByLibrary.simpleMessage( + "Kaynak kodumuz ve şifrelememiz harici olarak denetlenmiştir"), + "loadMessage6": MessageLookupByLibrary.simpleMessage( + "Albümlerinizin bağlantılarını sevdiklerinizle paylaşabilirsiniz"), + "loadMessage7": MessageLookupByLibrary.simpleMessage( + "Mobil uygulamalarımız, tıkladığınız yeni fotoğrafları şifrelemek ve yedeklemek için arka planda çalışır"), + "loadMessage8": MessageLookupByLibrary.simpleMessage( + "web.ente.io\'nun mükemmel bir yükleyicisi var"), + "loadMessage9": MessageLookupByLibrary.simpleMessage( + "Verilerinizi güvenli bir şekilde şifrelemek için Xchacha20Poly1305 kullanıyoruz"), + "loadingExifData": + MessageLookupByLibrary.simpleMessage("EXIF verileri yükleniyor..."), + "loadingGallery": + MessageLookupByLibrary.simpleMessage("Galeri yükleniyor..."), + "loadingMessage": MessageLookupByLibrary.simpleMessage( + "Fotoğraflarınız yükleniyor..."), + "loadingModel": + MessageLookupByLibrary.simpleMessage("Modeller indiriliyor..."), + "localGallery": MessageLookupByLibrary.simpleMessage("Yerel galeri"), + "location": MessageLookupByLibrary.simpleMessage("Konum"), + "locationName": MessageLookupByLibrary.simpleMessage("Konum Adı"), + "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage( + "Bir fotoğrafın belli bir yarıçapında çekilen fotoğrafları gruplandırın"), + "locations": MessageLookupByLibrary.simpleMessage("Konum"), + "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Kilit"), + "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage( + "Kilit ekranını aktif etmek için lütfen cihazın ayarlarından şifreyi ya da ekran kilidini ayarlayın."), + "lockscreen": MessageLookupByLibrary.simpleMessage("Kilit ekranı"), + "logInLabel": MessageLookupByLibrary.simpleMessage("Giriş yap"), + "loggingOut": + MessageLookupByLibrary.simpleMessage("Çıkış yapılıyor..."), + "loginSessionExpired": + MessageLookupByLibrary.simpleMessage("Session expired"), + "loginSessionExpiredDetails": MessageLookupByLibrary.simpleMessage( + "Your session has expired. Please login again."), + "loginTerms": MessageLookupByLibrary.simpleMessage( + "\"Giriş yap\" düğmesine tıklayarak, Hizmet Şartları\'nı ve Gizlilik Politikası\'nı kabul ediyorum"), + "logout": MessageLookupByLibrary.simpleMessage("Çıkış yap"), + "logsDialogBody": MessageLookupByLibrary.simpleMessage( + "Bu, sorununuzu gidermemize yardımcı olmak için günlükleri gönderecektir. Belirli dosyalarla ilgili sorunların izlenmesine yardımcı olmak için dosya adlarının ekleneceğini lütfen unutmayın."), + "longPressAnEmailToVerifyEndToEndEncryption": + MessageLookupByLibrary.simpleMessage( + "Uçtan uca şifrelemeyi doğrulamak için bir e-postaya uzun basın."), + "longpressOnAnItemToViewInFullscreen": + MessageLookupByLibrary.simpleMessage( + "Tam ekranda görüntülemek için bir öğeye uzun basın"), + "lostDevice": + MessageLookupByLibrary.simpleMessage("Cihazı kayıp mı ettiniz?"), + "machineLearning": + MessageLookupByLibrary.simpleMessage("Makine öğrenimi"), + "magicSearch": MessageLookupByLibrary.simpleMessage("Sihirli arama"), + "manage": MessageLookupByLibrary.simpleMessage("Yönet"), + "manageDeviceStorage": + MessageLookupByLibrary.simpleMessage("Cihaz depolamasını yönet"), + "manageFamily": MessageLookupByLibrary.simpleMessage("Aileyi yönet"), + "manageLink": MessageLookupByLibrary.simpleMessage("Linki yönet"), + "manageParticipants": MessageLookupByLibrary.simpleMessage("Yönet"), + "manageSubscription": + MessageLookupByLibrary.simpleMessage("Abonelikleri yönet"), + "manualPairDesc": MessageLookupByLibrary.simpleMessage( + "Pair with PIN works with any screen you wish to view your album on."), + "map": MessageLookupByLibrary.simpleMessage("Harita"), + "maps": MessageLookupByLibrary.simpleMessage("Haritalar"), + "mastodon": MessageLookupByLibrary.simpleMessage("Mastodon"), + "matrix": MessageLookupByLibrary.simpleMessage("Matrix"), + "memoryCount": m34, + "merchandise": MessageLookupByLibrary.simpleMessage("Ürünler"), + "mlIndexingDescription": MessageLookupByLibrary.simpleMessage( + "Please note that machine learning will result in a higher bandwidth and battery usage until all items are indexed."), + "mobileWebDesktop": + MessageLookupByLibrary.simpleMessage("Mobil, Web, Masaüstü"), + "moderateStrength": MessageLookupByLibrary.simpleMessage("Ilımlı"), + "modifyYourQueryOrTrySearchingFor": + MessageLookupByLibrary.simpleMessage( + "Sorgunuzu değiştirin veya aramayı deneyin"), + "moments": MessageLookupByLibrary.simpleMessage("Anlar"), + "monthly": MessageLookupByLibrary.simpleMessage("Aylık"), + "moveItem": m35, + "moveToAlbum": MessageLookupByLibrary.simpleMessage("Albüme taşı"), + "moveToHiddenAlbum": + MessageLookupByLibrary.simpleMessage("Gizli albüme ekle"), + "movedSuccessfullyTo": m36, + "movedToTrash": + MessageLookupByLibrary.simpleMessage("Cöp kutusuna taşı"), + "movingFilesToAlbum": MessageLookupByLibrary.simpleMessage( + "Dosyalar albüme taşınıyor..."), + "name": MessageLookupByLibrary.simpleMessage("İsim"), + "networkConnectionRefusedErr": MessageLookupByLibrary.simpleMessage( + "Ente\'ye bağlanılamıyor. Lütfen bir süre sonra tekrar deneyin. Hata devam ederse lütfen desteğe başvurun."), + "networkHostLookUpErr": MessageLookupByLibrary.simpleMessage( + "Ente\'ye bağlanılamıyor. Lütfen ağ ayarlarınızı kontrol edin ve hata devam ederse destek ekibiyle iletişime geçin."), + "never": MessageLookupByLibrary.simpleMessage("Asla"), + "newAlbum": MessageLookupByLibrary.simpleMessage("Yeni albüm"), + "newToEnte": MessageLookupByLibrary.simpleMessage("New to Ente"), + "newest": MessageLookupByLibrary.simpleMessage("En yeni"), + "next": MessageLookupByLibrary.simpleMessage("Next"), + "no": MessageLookupByLibrary.simpleMessage("Hayır"), + "noAlbumsSharedByYouYet": MessageLookupByLibrary.simpleMessage( + "Henüz paylaştığınız albüm yok"), + "noDeviceFound": + MessageLookupByLibrary.simpleMessage("No device found"), + "noDeviceLimit": MessageLookupByLibrary.simpleMessage("Yok"), + "noDeviceThatCanBeDeleted": MessageLookupByLibrary.simpleMessage( + "Bu cihazda silinebilecek hiçbir dosyanız yok"), + "noDuplicates": + MessageLookupByLibrary.simpleMessage("Yinelenenleri kaldır"), + "noExifData": MessageLookupByLibrary.simpleMessage("EXIF verisi yok"), + "noHiddenPhotosOrVideos": MessageLookupByLibrary.simpleMessage( + "Gizli fotoğraf veya video yok"), + "noImagesWithLocation": + MessageLookupByLibrary.simpleMessage("Konum içeren resim yok"), + "noInternetConnection": + MessageLookupByLibrary.simpleMessage("İnternet bağlantısı yok"), + "noPhotosAreBeingBackedUpRightNow": + MessageLookupByLibrary.simpleMessage( + "Şu anda hiçbir fotoğraf yedeklenmiyor"), + "noPhotosFoundHere": + MessageLookupByLibrary.simpleMessage("Burada fotoğraf bulunamadı"), + "noRecoveryKey": + MessageLookupByLibrary.simpleMessage("Kurtarma kodunuz yok mu?"), + "noRecoveryKeyNoDecryption": MessageLookupByLibrary.simpleMessage( + "Uçtan uca şifreleme protokolümüzün doğası gereği, verileriniz şifreniz veya kurtarma anahtarınız olmadan çözülemez"), + "noResults": MessageLookupByLibrary.simpleMessage("Sonuç bulunamadı"), + "noResultsFound": + MessageLookupByLibrary.simpleMessage("Hiçbir sonuç bulunamadı"), + "noSystemLockFound": + MessageLookupByLibrary.simpleMessage("No system lock found"), + "notPersonLabel": m37, + "nothingSharedWithYouYet": MessageLookupByLibrary.simpleMessage( + "Henüz sizinle paylaşılan bir şey yok"), + "nothingToSeeHere": MessageLookupByLibrary.simpleMessage( + "Burada görülecek bir şey yok! 👀"), + "notifications": MessageLookupByLibrary.simpleMessage("Bildirimler"), + "ok": MessageLookupByLibrary.simpleMessage("Tamam"), + "onDevice": MessageLookupByLibrary.simpleMessage("Bu cihaz"), + "onEnte": MessageLookupByLibrary.simpleMessage( + "ente üzerinde"), + "oops": MessageLookupByLibrary.simpleMessage("Hay aksi"), + "oopsCouldNotSaveEdits": MessageLookupByLibrary.simpleMessage( + "Hata! Düzenlemeler kaydedilemedi"), + "oopsSomethingWentWrong": MessageLookupByLibrary.simpleMessage( + "Hoop, Birşeyler yanlış gitti"), + "openSettings": MessageLookupByLibrary.simpleMessage("Ayarları Açın"), + "openTheItem": MessageLookupByLibrary.simpleMessage("• Öğeyi açın"), + "openstreetmapContributors": MessageLookupByLibrary.simpleMessage( + "© OpenStreetMap katkıda bululanlar"), + "optionalAsShortAsYouLike": MessageLookupByLibrary.simpleMessage( + "İsteğe bağlı, istediğiniz kadar kısa..."), + "orPickAnExistingOne": + MessageLookupByLibrary.simpleMessage("Veya mevcut birini seçiniz"), + "pair": MessageLookupByLibrary.simpleMessage("Eşleştir"), + "pairWithPin": MessageLookupByLibrary.simpleMessage("Pair with PIN"), + "pairingComplete": + MessageLookupByLibrary.simpleMessage("Pairing complete"), + "passKeyPendingVerification": MessageLookupByLibrary.simpleMessage( + "Verification is still pending"), + "passkey": MessageLookupByLibrary.simpleMessage("Parola Anahtarı"), + "passkeyAuthTitle": + MessageLookupByLibrary.simpleMessage("Geçiş anahtarı doğrulaması"), + "password": MessageLookupByLibrary.simpleMessage("Şifre"), + "passwordChangedSuccessfully": MessageLookupByLibrary.simpleMessage( + "Şifreniz başarılı bir şekilde değiştirildi"), + "passwordLock": MessageLookupByLibrary.simpleMessage("Sifre kilidi"), + "passwordStrength": m38, + "passwordWarning": MessageLookupByLibrary.simpleMessage( + "Şifrelerinizi saklamıyoruz, bu yüzden unutursanız, verilerinizi deşifre edemeyiz"), + "paymentDetails": + MessageLookupByLibrary.simpleMessage("Ödeme detayları"), + "paymentFailed": + MessageLookupByLibrary.simpleMessage("Ödeme başarısız oldu"), + "paymentFailedMessage": MessageLookupByLibrary.simpleMessage( + "Maalesef ödemeniz başarısız oldu. Lütfen destekle iletişime geçin, size yardımcı olacağız!"), + "paymentFailedTalkToProvider": m39, + "pendingItems": MessageLookupByLibrary.simpleMessage("Bekleyen Öğeler"), + "pendingSync": + MessageLookupByLibrary.simpleMessage("Senkronizasyon bekleniyor"), + "people": MessageLookupByLibrary.simpleMessage("People"), + "peopleUsingYourCode": + MessageLookupByLibrary.simpleMessage("Kodunuzu kullananlar"), + "permDeleteWarning": MessageLookupByLibrary.simpleMessage( + "Çöp kutusundaki tüm öğeler kalıcı olarak silinecek\n\nBu işlem geri alınamaz"), + "permanentlyDelete": + MessageLookupByLibrary.simpleMessage("Kalıcı olarak sil"), + "permanentlyDeleteFromDevice": MessageLookupByLibrary.simpleMessage( + "Cihazdan kalıcı olarak silinsin mi?"), + "photoDescriptions": + MessageLookupByLibrary.simpleMessage("Fotoğraf Açıklaması"), + "photoGridSize": + MessageLookupByLibrary.simpleMessage("Fotoğraf ızgara boyutu"), + "photoSmallCase": MessageLookupByLibrary.simpleMessage("fotoğraf"), + "photos": MessageLookupByLibrary.simpleMessage("Fotoğraflar"), + "photosAddedByYouWillBeRemovedFromTheAlbum": + MessageLookupByLibrary.simpleMessage( + "Eklediğiniz fotoğraflar albümden kaldırılacak"), + "pickCenterPoint": + MessageLookupByLibrary.simpleMessage("Merkez noktasını seçin"), + "pinAlbum": MessageLookupByLibrary.simpleMessage("Albümü sabitle"), + "pinLock": MessageLookupByLibrary.simpleMessage("PIN lock"), + "playOnTv": MessageLookupByLibrary.simpleMessage("Albümü TV\'de oynat"), + "playStoreFreeTrialValidTill": m40, + "playstoreSubscription": + MessageLookupByLibrary.simpleMessage("PlayStore aboneliği"), + "pleaseCheckYourInternetConnectionAndTryAgain": + MessageLookupByLibrary.simpleMessage( + "Lütfen internet bağlantınızı kontrol edin ve yeniden deneyin."), + "pleaseContactSupportAndWeWillBeHappyToHelp": + MessageLookupByLibrary.simpleMessage( + "Lütfen support@ente.io ile iletişime geçin; size yardımcı olmaktan memnuniyet duyarız!"), + "pleaseContactSupportIfTheProblemPersists": + MessageLookupByLibrary.simpleMessage( + "Bu hata devam ederse lütfen desteğe başvurun"), + "pleaseEmailUsAt": m41, + "pleaseGrantPermissions": + MessageLookupByLibrary.simpleMessage("Lütfen izin ver"), + "pleaseLoginAgain": + MessageLookupByLibrary.simpleMessage("Lütfen tekrar giriş yapın"), + "pleaseSendTheLogsTo": m42, + "pleaseTryAgain": + MessageLookupByLibrary.simpleMessage("Lütfen tekrar deneyiniz"), + "pleaseVerifyTheCodeYouHaveEntered": + MessageLookupByLibrary.simpleMessage( + "Lütfen girdiğiniz kodu doğrulayın"), + "pleaseWait": + MessageLookupByLibrary.simpleMessage("Lütfen bekleyiniz..."), + "pleaseWaitDeletingAlbum": MessageLookupByLibrary.simpleMessage( + "Lütfen bekleyin, albüm siliniyor"), + "pleaseWaitForSometimeBeforeRetrying": + MessageLookupByLibrary.simpleMessage( + "Tekrar denemeden önce lütfen bir süre bekleyin"), + "preparingLogs": + MessageLookupByLibrary.simpleMessage("Günlük hazırlanıyor..."), + "preserveMore": + MessageLookupByLibrary.simpleMessage("Daha fazlasını koruyun"), + "pressAndHoldToPlayVideo": MessageLookupByLibrary.simpleMessage( + "Videoları yönetmek için basılı tutun"), + "pressAndHoldToPlayVideoDetailed": MessageLookupByLibrary.simpleMessage( + "Videoyu oynatmak için resmi basılı tutun"), + "privacy": MessageLookupByLibrary.simpleMessage("Gizlilik"), + "privacyPolicyTitle": + MessageLookupByLibrary.simpleMessage("Mahremiyet Politikası"), + "privateBackups": + MessageLookupByLibrary.simpleMessage("Özel yedeklemeler"), + "privateSharing": MessageLookupByLibrary.simpleMessage("Özel paylaşım"), + "publicLinkCreated": MessageLookupByLibrary.simpleMessage( + "Herkese açık link oluşturuldu"), + "publicLinkEnabled": MessageLookupByLibrary.simpleMessage( + "Herkese açık bağlantı aktive edildi"), + "quickLinks": MessageLookupByLibrary.simpleMessage("Hızlı Erişim"), + "radius": MessageLookupByLibrary.simpleMessage("Yarıçap"), + "raiseTicket": MessageLookupByLibrary.simpleMessage("Bileti artır"), + "rateTheApp": + MessageLookupByLibrary.simpleMessage("Uygulamaya puan verin"), + "rateUs": MessageLookupByLibrary.simpleMessage("Bizi değerlendirin"), + "rateUsOnStore": m43, + "recover": MessageLookupByLibrary.simpleMessage("Kurtarma"), + "recoverAccount": MessageLookupByLibrary.simpleMessage("Hesabı kurtar"), + "recoverButton": MessageLookupByLibrary.simpleMessage("Kurtar"), + "recoveryKey": + MessageLookupByLibrary.simpleMessage("Kurtarma anahtarı"), + "recoveryKeyCopiedToClipboard": MessageLookupByLibrary.simpleMessage( + "Kurtarma anahtarınız panoya kopyalandı"), + "recoveryKeyOnForgotPassword": MessageLookupByLibrary.simpleMessage( + "Şifrenizi unutursanız, verilerinizi kurtarmanın tek yolu bu anahtar olacaktır."), + "recoveryKeySaveDescription": MessageLookupByLibrary.simpleMessage( + "Bu anahtarı saklamıyoruz, lütfen bu 24 kelime anahtarı güvenli bir yerde saklayın."), + "recoveryKeySuccessBody": MessageLookupByLibrary.simpleMessage( + "Harika! Kurtarma anahtarınız geçerlidir. Doğrulama için teşekkür ederim.\n\nLütfen kurtarma anahtarınızı güvenli bir şekilde yedeklediğinizden emin olun."), + "recoveryKeyVerified": + MessageLookupByLibrary.simpleMessage("Kurtarma kodu doğrulandı"), + "recoveryKeyVerifyReason": MessageLookupByLibrary.simpleMessage( + "Eğer şifrenizi unutursanız, fotoğraflarınızı kurtarmanın tek yolu kurtarma anahtarınızdır. Kurtarma anahtarınızı Ayarlar > Güvenlik bölümünde bulabilirsiniz.\n\nLütfen kurtarma anahtarınızı buraya girerek doğru bir şekilde kaydettiğinizi doğrulayın."), + "recoverySuccessful": + MessageLookupByLibrary.simpleMessage("Kurtarma başarılı!"), + "recreatePasswordBody": MessageLookupByLibrary.simpleMessage( + "Cihazınız, şifrenizi doğrulamak için yeterli güce sahip değil, ancak tüm cihazlarda çalışacak şekilde yeniden oluşturabiliriz.\n\nLütfen kurtarma anahtarınızı kullanarak giriş yapın ve şifrenizi yeniden oluşturun (istediğiniz takdirde aynı şifreyi tekrar kullanabilirsiniz)."), + "recreatePasswordTitle": MessageLookupByLibrary.simpleMessage( + "Sifrenizi tekrardan oluşturun"), + "reddit": MessageLookupByLibrary.simpleMessage("Reddit"), + "reenterPassword": + MessageLookupByLibrary.simpleMessage("Re-enter password"), + "reenterPin": MessageLookupByLibrary.simpleMessage("Re-enter PIN"), + "referFriendsAnd2xYourPlan": MessageLookupByLibrary.simpleMessage( + "Arkadaşlarınıza önerin ve planınızı 2 katına çıkarın"), + "referralStep1": MessageLookupByLibrary.simpleMessage( + "1. Bu kodu arkadaşlarınıza verin"), + "referralStep2": MessageLookupByLibrary.simpleMessage( + "2. Ücretli bir plan için kaydolsunlar"), + "referralStep3": m44, + "referrals": MessageLookupByLibrary.simpleMessage("Referanslar"), + "referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage( + "Davetler şu anda durmuş durumda"), + "remindToEmptyDeviceTrash": MessageLookupByLibrary.simpleMessage( + "Ayrıca boşalan alanı talep etmek için \"Ayarlar\" -> \"Depolama\" bölümünden \"Son Silinenler \"i boşaltın"), + "remindToEmptyEnteTrash": MessageLookupByLibrary.simpleMessage( + "Ayrıca boşalan alana sahip olmak için \"Çöp Kutunuzu\" boşaltın"), + "remoteImages": + MessageLookupByLibrary.simpleMessage("Uzaktan Görüntüler"), + "remoteThumbnails": + MessageLookupByLibrary.simpleMessage("Uzak Küçük Resim"), + "remoteVideos": MessageLookupByLibrary.simpleMessage("Uzak videolar"), + "remove": MessageLookupByLibrary.simpleMessage("Kaldır"), + "removeDuplicates": + MessageLookupByLibrary.simpleMessage("Yinelenenleri kaldır"), + "removeDuplicatesDesc": MessageLookupByLibrary.simpleMessage( + "Review and remove files that are exact duplicates."), + "removeFromAlbum": + MessageLookupByLibrary.simpleMessage("Albümden çıkar"), + "removeFromAlbumTitle": + MessageLookupByLibrary.simpleMessage("Albümden çıkarılsın mı?"), + "removeFromFavorite": + MessageLookupByLibrary.simpleMessage("Favorilerimden kaldır"), + "removeLink": MessageLookupByLibrary.simpleMessage("Linki kaldır"), + "removeParticipant": + MessageLookupByLibrary.simpleMessage("Katılımcıyı kaldır"), + "removeParticipantBody": m45, + "removePersonLabel": + MessageLookupByLibrary.simpleMessage("Remove person label"), + "removePublicLink": + MessageLookupByLibrary.simpleMessage("Herkese açık link oluştur"), + "removeShareItemsWarning": MessageLookupByLibrary.simpleMessage( + "Kaldırdığınız öğelerden bazıları başkaları tarafından eklenmiştir ve bunlara erişiminizi kaybedeceksiniz"), + "removeWithQuestionMark": + MessageLookupByLibrary.simpleMessage("Kaldır?"), + "removingFromFavorites": + MessageLookupByLibrary.simpleMessage("Favorilerimden kaldır..."), + "rename": MessageLookupByLibrary.simpleMessage("Yeniden adlandır"), + "renameAlbum": + MessageLookupByLibrary.simpleMessage("Albümü yeniden adlandır"), + "renameFile": + MessageLookupByLibrary.simpleMessage("Dosyayı yeniden adlandır"), + "renewSubscription": + MessageLookupByLibrary.simpleMessage("Abonelik yenileme"), + "renewsOn": m46, + "reportABug": MessageLookupByLibrary.simpleMessage("Hatayı bildir"), + "reportBug": MessageLookupByLibrary.simpleMessage("Hata bildir"), + "resendEmail": + MessageLookupByLibrary.simpleMessage("E-postayı yeniden gönder"), + "resetIgnoredFiles": MessageLookupByLibrary.simpleMessage( + "Yok sayılan dosyaları sıfırla"), + "resetPasswordTitle": + MessageLookupByLibrary.simpleMessage("Parolanızı sıfırlayın"), + "resetToDefault": + MessageLookupByLibrary.simpleMessage("Varsayılana sıfırla"), + "restore": MessageLookupByLibrary.simpleMessage("Yenile"), + "restoreToAlbum": MessageLookupByLibrary.simpleMessage("Albümü yenile"), + "restoringFiles": + MessageLookupByLibrary.simpleMessage("Dosyalar geri yükleniyor..."), + "retry": MessageLookupByLibrary.simpleMessage("Tekrar dene"), + "reviewDeduplicateItems": MessageLookupByLibrary.simpleMessage( + "Lütfen kopya olduğunu düşündüğünüz öğeleri inceleyin ve silin."), + "reviewSuggestions": + MessageLookupByLibrary.simpleMessage("Review suggestions"), + "right": MessageLookupByLibrary.simpleMessage("Right"), + "rotate": MessageLookupByLibrary.simpleMessage("Rotate"), + "rotateLeft": MessageLookupByLibrary.simpleMessage("Sola döndür"), + "rotateRight": MessageLookupByLibrary.simpleMessage("Sağa döndür"), + "safelyStored": + MessageLookupByLibrary.simpleMessage("Güvenle saklanır"), + "save": MessageLookupByLibrary.simpleMessage("Kaydet"), + "saveCollage": MessageLookupByLibrary.simpleMessage("Kolajı kaydet"), + "saveCopy": MessageLookupByLibrary.simpleMessage("Kopyasını kaydet"), + "saveKey": MessageLookupByLibrary.simpleMessage("Anahtarı kaydet"), + "saveYourRecoveryKeyIfYouHaventAlready": + MessageLookupByLibrary.simpleMessage( + "Henüz yapmadıysanız kurtarma anahtarınızı kaydetmeyi unutmayın"), + "saving": MessageLookupByLibrary.simpleMessage("Kaydediliyor..."), + "savingEdits": MessageLookupByLibrary.simpleMessage("Saving edits..."), + "scanCode": MessageLookupByLibrary.simpleMessage("Kodu tarayın"), + "scanThisBarcodeWithnyourAuthenticatorApp": + MessageLookupByLibrary.simpleMessage( + "Kimlik doğrulama uygulamanız ile kodu tarayın"), + "search": MessageLookupByLibrary.simpleMessage("Search"), + "searchAlbumsEmptySection": + MessageLookupByLibrary.simpleMessage("Albümler"), + "searchByAlbumNameHint": + MessageLookupByLibrary.simpleMessage("Albüm adı"), + "searchByExamples": MessageLookupByLibrary.simpleMessage( + "• Albüm adları (ör. \"Kamera\")\n• Dosya türleri (ör. \"Videolar\", \".gif\")\n• Yıllar ve aylar (ör. \"2022\", \"Ocak\")\n• Tatiller (ör. \"Noel\")\n• Fotoğraf açıklamaları (ör. \"#eğlence\")"), + "searchCaptionEmptySection": MessageLookupByLibrary.simpleMessage( + "Fotoğraf bilgilerini burada hızlı bir şekilde bulmak için \"#trip\" gibi açıklamalar ekleyin"), + "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( + "Tarihe, aya veya yıla göre arama yapın"), + "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( + "People will be shown here once indexing is done"), + "searchFileTypesAndNamesEmptySection": + MessageLookupByLibrary.simpleMessage("Dosya türleri ve adları"), + "searchHint1": + MessageLookupByLibrary.simpleMessage("Hızlı, cihaz üzerinde arama"), + "searchHint2": MessageLookupByLibrary.simpleMessage( + "Fotoğraf tarihleri, açıklamalar"), + "searchHint3": MessageLookupByLibrary.simpleMessage( + "Albümler, dosya adları ve türleri"), + "searchHint4": MessageLookupByLibrary.simpleMessage("Konum"), + "searchHint5": MessageLookupByLibrary.simpleMessage( + "Çok yakında: Yüzler ve sihirli arama ✨"), + "searchLocationEmptySection": MessageLookupByLibrary.simpleMessage( + "Bir fotoğrafın belli bir yarıçapında çekilen fotoğrafları gruplandırın"), + "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( + "İnsanları davet ettiğinizde onların paylaştığı tüm fotoğrafları burada göreceksiniz"), + "searchResultCount": m47, + "security": MessageLookupByLibrary.simpleMessage("Güvenlik"), + "selectALocation": + MessageLookupByLibrary.simpleMessage("Bir konum seçin"), + "selectALocationFirst": + MessageLookupByLibrary.simpleMessage("Önce yeni yer seçin"), + "selectAlbum": MessageLookupByLibrary.simpleMessage("Albüm seçin"), + "selectAll": MessageLookupByLibrary.simpleMessage("Hepsini seç"), + "selectFoldersForBackup": MessageLookupByLibrary.simpleMessage( + "Yedekleme için klasörleri seçin"), + "selectItemsToAdd": + MessageLookupByLibrary.simpleMessage("Eklenecek eşyaları seçin"), + "selectLanguage": MessageLookupByLibrary.simpleMessage("Dil Seçin"), + "selectMorePhotos": + MessageLookupByLibrary.simpleMessage("Daha Fazla Fotoğraf Seç"), + "selectReason": + MessageLookupByLibrary.simpleMessage("Ayrılma nedeninizi seçin"), + "selectYourPlan": + MessageLookupByLibrary.simpleMessage("Planınızı seçin"), + "selectedFilesAreNotOnEnte": MessageLookupByLibrary.simpleMessage( + "Selected files are not on Ente"), + "selectedFoldersWillBeEncryptedAndBackedUp": + MessageLookupByLibrary.simpleMessage( + "Seçilen klasörler şifrelenecek ve yedeklenecektir"), + "selectedItemsWillBeDeletedFromAllAlbumsAndMoved": + MessageLookupByLibrary.simpleMessage( + "Seçilen öğeler tüm albümlerden silinecek ve çöp kutusuna taşınacak."), + "selectedPhotos": m48, + "selectedPhotosWithYours": m49, + "send": MessageLookupByLibrary.simpleMessage("Gönder"), + "sendEmail": MessageLookupByLibrary.simpleMessage("E-posta gönder"), + "sendInvite": MessageLookupByLibrary.simpleMessage("Davet kodu gönder"), + "sendLink": MessageLookupByLibrary.simpleMessage("Link gönder"), + "serverEndpoint": + MessageLookupByLibrary.simpleMessage("Sunucu uç noktası"), + "sessionExpired": + MessageLookupByLibrary.simpleMessage("Oturum süresi doldu"), + "setAPassword": MessageLookupByLibrary.simpleMessage("Şifre ayarla"), + "setAs": MessageLookupByLibrary.simpleMessage("Şu şekilde ayarla"), + "setCover": MessageLookupByLibrary.simpleMessage("Kapak Belirle"), + "setLabel": MessageLookupByLibrary.simpleMessage("Ayarla"), + "setNewPassword": + MessageLookupByLibrary.simpleMessage("Set new password"), + "setNewPin": MessageLookupByLibrary.simpleMessage("Set new PIN"), + "setPasswordTitle": + MessageLookupByLibrary.simpleMessage("Parola ayarlayın"), + "setRadius": MessageLookupByLibrary.simpleMessage("Yarıçapı ayarla"), + "setupComplete": + MessageLookupByLibrary.simpleMessage("Ayarlama işlemi başarılı"), + "share": MessageLookupByLibrary.simpleMessage("Paylaş"), + "shareALink": MessageLookupByLibrary.simpleMessage("Linki paylaş"), + "shareAlbumHint": MessageLookupByLibrary.simpleMessage( + "Bir albüm açın ve paylaşmak için sağ üstteki paylaş düğmesine dokunun."), + "shareAnAlbumNow": + MessageLookupByLibrary.simpleMessage("Şimdi bir albüm paylaşın"), + "shareLink": MessageLookupByLibrary.simpleMessage("Linki paylaş"), + "shareMyVerificationID": m50, + "shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage( + "Yalnızca istediğiniz kişilerle paylaşın"), + "shareTextConfirmOthersVerificationID": m51, + "shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage( + "Download Ente so we can easily share original quality photos and videos\n\nhttps://ente.io"), + "shareTextReferralCode": m52, + "shareWithNonenteUsers": + MessageLookupByLibrary.simpleMessage("Share with non-Ente users"), + "shareWithPeopleSectionTitle": m53, + "shareYourFirstAlbum": + MessageLookupByLibrary.simpleMessage("İlk albümünüzü paylaşın"), + "sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage( + "Create shared and collaborative albums with other Ente users, including users on free plans."), + "sharedByMe": + MessageLookupByLibrary.simpleMessage("Benim paylaştıklarım"), + "sharedByYou": MessageLookupByLibrary.simpleMessage("Paylaştıklarınız"), + "sharedPhotoNotifications": MessageLookupByLibrary.simpleMessage( + "Paylaşılan fotoğrafları ekle"), + "sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage( + "Birisi sizin de parçası olduğunuz paylaşılan bir albüme fotoğraf eklediğinde bildirim alın"), + "sharedWith": m54, + "sharedWithMe": + MessageLookupByLibrary.simpleMessage("Benimle paylaşılan"), + "sharedWithYou": + MessageLookupByLibrary.simpleMessage("Sizinle paylaşıldı"), + "sharing": MessageLookupByLibrary.simpleMessage("Paylaşılıyor..."), + "showMemories": MessageLookupByLibrary.simpleMessage("Anıları göster"), + "signOutFromOtherDevices": + MessageLookupByLibrary.simpleMessage("Diğer cihazlardan çıkış yap"), + "signOutOtherBody": MessageLookupByLibrary.simpleMessage( + "Eğer başka birisinin parolanızı bildiğini düşünüyorsanız, diğer tüm cihazları hesabınızdan çıkışa zorlayabilirsiniz."), + "signOutOtherDevices": + MessageLookupByLibrary.simpleMessage("Diğer cihazlardan çıkış yap"), + "signUpTerms": MessageLookupByLibrary.simpleMessage( + "Hizmet Şartları\'nı ve Gizlilik Politikası\'nı kabul ediyorum"), + "singleFileDeleteFromDevice": m55, + "singleFileDeleteHighlight": + MessageLookupByLibrary.simpleMessage("Tüm albümlerden silinecek."), + "singleFileInBothLocalAndRemote": m56, + "singleFileInRemoteOnly": m57, + "skip": MessageLookupByLibrary.simpleMessage("Geç"), + "social": MessageLookupByLibrary.simpleMessage("Sosyal Medya"), + "someItemsAreInBothEnteAndYourDevice": + MessageLookupByLibrary.simpleMessage( + "Some items are in both Ente and your device."), + "someOfTheFilesYouAreTryingToDeleteAre": + MessageLookupByLibrary.simpleMessage( + "Silmeye çalıştığınız dosyalardan bazıları yalnızca cihazınızda mevcuttur ve silindiği takdirde kurtarılamaz"), + "someoneSharingAlbumsWithYouShouldSeeTheSameId": + MessageLookupByLibrary.simpleMessage( + "Size albümleri paylaşan biri, kendi cihazında aynı kimliği görmelidir."), + "somethingWentWrong": + MessageLookupByLibrary.simpleMessage("Bazı şeyler yanlış gitti"), + "somethingWentWrongPleaseTryAgain": + MessageLookupByLibrary.simpleMessage( + "Bir şeyler ters gitti, lütfen tekrar deneyin"), + "sorry": MessageLookupByLibrary.simpleMessage("Üzgünüz"), + "sorryCouldNotAddToFavorites": MessageLookupByLibrary.simpleMessage( + "Üzgünüm, favorilere ekleyemedim!"), + "sorryCouldNotRemoveFromFavorites": + MessageLookupByLibrary.simpleMessage( + "Üzgünüm, favorilere ekleyemedim!"), + "sorryTheCodeYouveEnteredIsIncorrect": + MessageLookupByLibrary.simpleMessage( + "Üzgünüz, girdiğiniz kod yanlış"), + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": + MessageLookupByLibrary.simpleMessage( + "Üzgünüm, bu cihazda güvenli anahtarlarını oluşturamadık.\n\nLütfen başka bir cihazdan giriş yapmayı deneyiniz."), + "sortAlbumsBy": MessageLookupByLibrary.simpleMessage("Sırala"), + "sortNewestFirst": + MessageLookupByLibrary.simpleMessage("Yeniden eskiye"), + "sortOldestFirst": MessageLookupByLibrary.simpleMessage("Önce en eski"), + "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Başarılı"), + "startBackup": + MessageLookupByLibrary.simpleMessage("Yedeklemeyi başlat"), + "status": MessageLookupByLibrary.simpleMessage("Durum"), + "stopCastingBody": MessageLookupByLibrary.simpleMessage( + "Do you want to stop casting?"), + "stopCastingTitle": + MessageLookupByLibrary.simpleMessage("Stop casting"), + "storage": MessageLookupByLibrary.simpleMessage("Depolama"), + "storageBreakupFamily": MessageLookupByLibrary.simpleMessage("Aile"), + "storageBreakupYou": MessageLookupByLibrary.simpleMessage("Sen"), + "storageInGB": m58, + "storageLimitExceeded": + MessageLookupByLibrary.simpleMessage("Depolama sınırı aşıldı"), + "storageUsageInfo": m59, + "strongStrength": MessageLookupByLibrary.simpleMessage("Güçlü"), + "subAlreadyLinkedErrMessage": m60, + "subWillBeCancelledOn": m61, + "subscribe": MessageLookupByLibrary.simpleMessage("Abone ol"), + "subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage( + "Aboneliğinizin süresi dolmuş gibi görünüyor. Paylaşımı etkinleştirmek için lütfen abone olun."), + "subscription": MessageLookupByLibrary.simpleMessage("Abonelik"), + "success": MessageLookupByLibrary.simpleMessage("Başarılı"), + "successfullyArchived": + MessageLookupByLibrary.simpleMessage("Başarıyla arşivlendi"), + "successfullyHid": + MessageLookupByLibrary.simpleMessage("Başarıyla saklandı"), + "successfullyUnarchived": MessageLookupByLibrary.simpleMessage( + "Başarıyla arşivden çıkarıldı"), + "successfullyUnhid": MessageLookupByLibrary.simpleMessage( + "Başarıyla arşivden çıkarıldı"), + "suggestFeatures": + MessageLookupByLibrary.simpleMessage("Özellik önerin"), + "support": MessageLookupByLibrary.simpleMessage("Destek"), + "syncProgress": m62, + "syncStopped": + MessageLookupByLibrary.simpleMessage("Senkronizasyon durduruldu"), + "syncing": MessageLookupByLibrary.simpleMessage("Eşitleniyor..."), + "systemTheme": MessageLookupByLibrary.simpleMessage("Sistem"), + "tapToCopy": + MessageLookupByLibrary.simpleMessage("kopyalamak için dokunun"), + "tapToEnterCode": + MessageLookupByLibrary.simpleMessage("Kodu girmek icin tıklayın"), + "tapToUnlock": MessageLookupByLibrary.simpleMessage("Tap to unlock"), + "tempErrorContactSupportIfPersists": MessageLookupByLibrary.simpleMessage( + "Bir şeyler ters gitmiş gibi görünüyor. Lütfen bir süre sonra tekrar deneyin. Hata devam ederse, lütfen destek ekibimizle iletişime geçin."), + "terminate": MessageLookupByLibrary.simpleMessage("Sonlandır"), + "terminateSession": + MessageLookupByLibrary.simpleMessage("Oturumu sonlandır?"), + "terms": MessageLookupByLibrary.simpleMessage("Şartlar"), + "termsOfServicesTitle": MessageLookupByLibrary.simpleMessage("Şartlar"), + "thankYou": MessageLookupByLibrary.simpleMessage("Teşekkürler"), + "thankYouForSubscribing": MessageLookupByLibrary.simpleMessage( + "Abone olduğunuz için teşekkürler!"), + "theDownloadCouldNotBeCompleted": MessageLookupByLibrary.simpleMessage( + "İndirme işlemi tamamlanamadı"), + "theRecoveryKeyYouEnteredIsIncorrect": + MessageLookupByLibrary.simpleMessage( + "Girdiğiniz kurtarma kodu yanlış"), + "theme": MessageLookupByLibrary.simpleMessage("Tema"), + "theseItemsWillBeDeletedFromYourDevice": + MessageLookupByLibrary.simpleMessage( + "Bu öğeler cihazınızdan silinecektir."), + "theyAlsoGetXGb": m63, + "theyWillBeDeletedFromAllAlbums": + MessageLookupByLibrary.simpleMessage("Tüm albümlerden silinecek."), + "thisActionCannotBeUndone": + MessageLookupByLibrary.simpleMessage("Bu eylem geri alınamaz"), + "thisAlbumAlreadyHDACollaborativeLink": + MessageLookupByLibrary.simpleMessage( + "Bu albümde zaten bir ortak çalışma bağlantısı var"), + "thisCanBeUsedToRecoverYourAccountIfYou": + MessageLookupByLibrary.simpleMessage( + "Bu, iki faktörünüzü kaybederseniz hesabınızı kurtarmak için kullanılabilir"), + "thisDevice": MessageLookupByLibrary.simpleMessage("Bu cihaz"), + "thisEmailIsAlreadyInUse": MessageLookupByLibrary.simpleMessage( + "Bu e-posta zaten kullanılıyor"), + "thisImageHasNoExifData": + MessageLookupByLibrary.simpleMessage("Bu görselde exif verisi yok"), + "thisIsPersonVerificationId": m64, + "thisIsYourVerificationId": + MessageLookupByLibrary.simpleMessage("Doğrulama kimliğiniz"), + "thisWillLogYouOutOfTheFollowingDevice": + MessageLookupByLibrary.simpleMessage( + "Bu, sizi aşağıdaki cihazdan çıkış yapacak:"), + "thisWillLogYouOutOfThisDevice": MessageLookupByLibrary.simpleMessage( + "Bu cihazdaki oturumunuz kapatılacak!"), + "toEnableAppLockPleaseSetupDevicePasscodeOrScreen": + MessageLookupByLibrary.simpleMessage( + "To enable app lock, please setup device passcode or screen lock in your system settings."), + "toHideAPhotoOrVideo": MessageLookupByLibrary.simpleMessage( + "Bir fotoğrafı veya videoyu gizlemek için"), + "toResetVerifyEmail": MessageLookupByLibrary.simpleMessage( + "Şifrenizi sıfılamak için lütfen e-postanızı girin."), + "todaysLogs": + MessageLookupByLibrary.simpleMessage("Bugünün günlükleri"), + "tooManyIncorrectAttempts": + MessageLookupByLibrary.simpleMessage("Too many incorrect attempts"), + "total": MessageLookupByLibrary.simpleMessage("total"), + "totalSize": MessageLookupByLibrary.simpleMessage("Toplam boyut"), + "trash": MessageLookupByLibrary.simpleMessage("Cöp kutusu"), + "trashDaysLeft": m65, + "trim": MessageLookupByLibrary.simpleMessage("Trim"), + "tryAgain": MessageLookupByLibrary.simpleMessage("Tekrar deneyiniz"), + "turnOnBackupForAutoUpload": MessageLookupByLibrary.simpleMessage( + "Turn on backup to automatically upload files added to this device folder to Ente."), + "twitter": MessageLookupByLibrary.simpleMessage("Twitter"), + "twoMonthsFreeOnYearlyPlans": MessageLookupByLibrary.simpleMessage( + "Yıllık planlarda 2 ay ücretsiz"), + "twofactor": MessageLookupByLibrary.simpleMessage("İki faktör"), + "twofactorAuthenticationHasBeenDisabled": + MessageLookupByLibrary.simpleMessage( + "İki faktörlü kimlik doğrulama devre dışı"), + "twofactorAuthenticationPageTitle": + MessageLookupByLibrary.simpleMessage("İki faktörlü doğrulama"), + "twofactorAuthenticationSuccessfullyReset": + MessageLookupByLibrary.simpleMessage( + "İki faktörlü kimlik doğrulama başarıyla sıfırlandı"), + "twofactorSetup": + MessageLookupByLibrary.simpleMessage("Cift faktör ayarı"), + "unarchive": MessageLookupByLibrary.simpleMessage("Arşivden cıkar"), + "unarchiveAlbum": + MessageLookupByLibrary.simpleMessage("Arşivden Çıkar"), + "unarchiving": + MessageLookupByLibrary.simpleMessage("Arşivden çıkarılıyor..."), + "uncategorized": MessageLookupByLibrary.simpleMessage("Kategorisiz"), + "unhide": MessageLookupByLibrary.simpleMessage("Gizleme"), + "unhideToAlbum": MessageLookupByLibrary.simpleMessage("Albümü gizleme"), + "unhiding": MessageLookupByLibrary.simpleMessage("Gösteriliyor..."), + "unhidingFilesToAlbum": MessageLookupByLibrary.simpleMessage( + "Albümdeki dosyalar gösteriliyor"), + "unlock": MessageLookupByLibrary.simpleMessage("Kilidi aç"), + "unpinAlbum": MessageLookupByLibrary.simpleMessage( + "Albümün sabitlemesini kaldır"), + "unselectAll": + MessageLookupByLibrary.simpleMessage("Tümünün seçimini kaldır"), + "update": MessageLookupByLibrary.simpleMessage("Güncelle"), + "updateAvailable": + MessageLookupByLibrary.simpleMessage("Güncelleme mevcut"), + "updatingFolderSelection": MessageLookupByLibrary.simpleMessage( + "Klasör seçimi güncelleniyor..."), + "upgrade": MessageLookupByLibrary.simpleMessage("Yükselt"), + "uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage( + "Dosyalar albüme taşınıyor..."), + "upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage( + "4 Aralık\'a kadar %50\'ye varan indirim."), + "usableReferralStorageInfo": MessageLookupByLibrary.simpleMessage( + "Kullanılabilir depolama alanı mevcut planınızla sınırlıdır. Talep edilen fazla depolama alanı, planınızı yükselttiğinizde otomatik olarak kullanılabilir hale gelecektir."), + "useAsCover": MessageLookupByLibrary.simpleMessage("Use as cover"), + "usePublicLinksForPeopleNotOnEnte": + MessageLookupByLibrary.simpleMessage( + "Use public links for people not on Ente"), + "useRecoveryKey": + MessageLookupByLibrary.simpleMessage("Kurtarma anahtarını kullan"), + "useSelectedPhoto": + MessageLookupByLibrary.simpleMessage("Seçilen fotoğrafı kullan"), + "usedSpace": MessageLookupByLibrary.simpleMessage("Kullanılan alan"), + "validTill": m66, + "verificationFailedPleaseTryAgain": + MessageLookupByLibrary.simpleMessage( + "Doğrulama başarısız oldu, lütfen tekrar deneyin"), + "verificationId": + MessageLookupByLibrary.simpleMessage("Doğrulama kimliği"), + "verify": MessageLookupByLibrary.simpleMessage("Doğrula"), + "verifyEmail": + MessageLookupByLibrary.simpleMessage("E-posta adresini doğrulayın"), + "verifyEmailID": m67, + "verifyIDLabel": MessageLookupByLibrary.simpleMessage("Doğrula"), + "verifyPasskey": + MessageLookupByLibrary.simpleMessage("Şifrenizi doğrulayın"), + "verifyPassword": + MessageLookupByLibrary.simpleMessage("Şifrenizi doğrulayın"), + "verifying": MessageLookupByLibrary.simpleMessage("Doğrulanıyor..."), + "verifyingRecoveryKey": MessageLookupByLibrary.simpleMessage( + "Kurtarma kodu doğrulanıyor..."), + "videoSmallCase": MessageLookupByLibrary.simpleMessage("video"), + "videos": MessageLookupByLibrary.simpleMessage("Videolar"), + "viewActiveSessions": + MessageLookupByLibrary.simpleMessage("Aktif oturumları görüntüle"), + "viewAddOnButton": + MessageLookupByLibrary.simpleMessage("Eklentileri görüntüle"), + "viewAll": MessageLookupByLibrary.simpleMessage("Tümünü görüntüle"), + "viewAllExifData": MessageLookupByLibrary.simpleMessage( + "Tüm EXIF verilerini görüntüle"), + "viewLargeFiles": MessageLookupByLibrary.simpleMessage("Large files"), + "viewLargeFilesDesc": MessageLookupByLibrary.simpleMessage( + "View files that are consuming the most amount of storage"), + "viewLogs": MessageLookupByLibrary.simpleMessage("Günlükleri göster"), + "viewRecoveryKey": MessageLookupByLibrary.simpleMessage( + "Kurtarma anahtarını görüntüle"), + "viewer": MessageLookupByLibrary.simpleMessage("Görüntüleyici"), + "visitWebToManage": MessageLookupByLibrary.simpleMessage( + "Aboneliğinizi yönetmek için lütfen web.ente.io adresini ziyaret edin"), + "waitingForVerification": + MessageLookupByLibrary.simpleMessage("Doğrulama bekleniyor..."), + "waitingForWifi": + MessageLookupByLibrary.simpleMessage("WiFi bekleniyor..."), + "weAreOpenSource": + MessageLookupByLibrary.simpleMessage("Biz açık kaynağız!"), + "weDontSupportEditingPhotosAndAlbumsThatYouDont": + MessageLookupByLibrary.simpleMessage( + "Henüz sahibi olmadığınız fotoğraf ve albümlerin düzenlenmesini desteklemiyoruz"), + "weHaveSendEmailTo": m68, + "weakStrength": MessageLookupByLibrary.simpleMessage("Zayıf"), + "welcomeBack": + MessageLookupByLibrary.simpleMessage("Tekrardan hoşgeldin!"), + "whatsNew": MessageLookupByLibrary.simpleMessage("What\'s new"), + "yearly": MessageLookupByLibrary.simpleMessage("Yıllık"), + "yearsAgo": m69, + "yes": MessageLookupByLibrary.simpleMessage("Evet"), + "yesCancel": MessageLookupByLibrary.simpleMessage("Evet, iptal et"), + "yesConvertToViewer": MessageLookupByLibrary.simpleMessage( + "Evet, görüntüleyici olarak dönüştür"), + "yesDelete": MessageLookupByLibrary.simpleMessage("Evet, sil"), + "yesDiscardChanges": + MessageLookupByLibrary.simpleMessage("Evet, değişiklikleri sil"), + "yesLogout": + MessageLookupByLibrary.simpleMessage("Evet, oturumu kapat"), + "yesRemove": MessageLookupByLibrary.simpleMessage("Evet, sil"), + "yesRenew": MessageLookupByLibrary.simpleMessage("Evet, yenile"), + "you": MessageLookupByLibrary.simpleMessage("Sen"), + "youAreOnAFamilyPlan": + MessageLookupByLibrary.simpleMessage("Aile planı kullanıyorsunuz!"), + "youAreOnTheLatestVersion": + MessageLookupByLibrary.simpleMessage("En son sürüme sahipsiniz"), + "youCanAtMaxDoubleYourStorage": MessageLookupByLibrary.simpleMessage( + "* Alanınızı en fazla ikiye katlayabilirsiniz"), + "youCanManageYourLinksInTheShareTab": + MessageLookupByLibrary.simpleMessage( + "Bağlantılarınızı paylaşım sekmesinden yönetebilirsiniz."), + "youCanTrySearchingForADifferentQuery": + MessageLookupByLibrary.simpleMessage( + "Farklı bir sorgu aramayı deneyebilirsiniz."), + "youCannotDowngradeToThisPlan": + MessageLookupByLibrary.simpleMessage("Bu plana geçemezsiniz"), + "youCannotShareWithYourself": + MessageLookupByLibrary.simpleMessage("Kendinizle paylaşamazsınız"), + "youDontHaveAnyArchivedItems": + MessageLookupByLibrary.simpleMessage("Arşivlenmiş öğeniz yok."), + "youHaveSuccessfullyFreedUp": m70, + "yourAccountHasBeenDeleted": + MessageLookupByLibrary.simpleMessage("Hesabınız silindi"), + "yourMap": MessageLookupByLibrary.simpleMessage("Haritalarınız"), + "yourPlanWasSuccessfullyDowngraded": + MessageLookupByLibrary.simpleMessage( + "Planınız başarıyla düşürüldü"), + "yourPlanWasSuccessfullyUpgraded": MessageLookupByLibrary.simpleMessage( + "Planınız başarılı şekilde yükseltildi"), + "yourPurchaseWasSuccessful": + MessageLookupByLibrary.simpleMessage("Satın alım başarılı"), + "yourStorageDetailsCouldNotBeFetched": + MessageLookupByLibrary.simpleMessage("Depolama bilgisi alınamadı"), + "yourSubscriptionHasExpired": + MessageLookupByLibrary.simpleMessage("Aboneliğinizin süresi doldu"), + "yourSubscriptionWasUpdatedSuccessfully": + MessageLookupByLibrary.simpleMessage( + "Aboneliğiniz başarıyla güncellendi"), + "yourVerificationCodeHasExpired": MessageLookupByLibrary.simpleMessage( + "Doğrulama kodunuzun süresi doldu"), + "youveNoDuplicateFilesThatCanBeCleared": + MessageLookupByLibrary.simpleMessage( + "Temizlenebilecek yinelenen dosyalarınız yok"), + "youveNoFilesInThisAlbumThatCanBeDeleted": + MessageLookupByLibrary.simpleMessage( + "Bu cihazda silinebilecek hiçbir dosyanız yok"), + "zoomOutToSeePhotos": MessageLookupByLibrary.simpleMessage( + "Fotoğrafları görmek için uzaklaştırın") + }; +} diff --git a/mobile/lib/generated/l10n.dart b/mobile/lib/generated/l10n.dart index 017e4378fd..3ae7fb3ded 100644 --- a/mobile/lib/generated/l10n.dart +++ b/mobile/lib/generated/l10n.dart @@ -9123,6 +9123,7 @@ class AppLocalizationDelegate extends LocalizationsDelegate { Locale.fromSubtags(languageCode: 'pl'), Locale.fromSubtags(languageCode: 'pt'), Locale.fromSubtags(languageCode: 'ru'), + Locale.fromSubtags(languageCode: 'tr'), Locale.fromSubtags(languageCode: 'zh'), ]; } diff --git a/mobile/lib/l10n/intl_en.arb b/mobile/lib/l10n/intl_en.arb index b07faf91b9..7066a1c919 100644 --- a/mobile/lib/l10n/intl_en.arb +++ b/mobile/lib/l10n/intl_en.arb @@ -1,4 +1,5 @@ { + "@@locale ": "en", "enterYourEmailAddress": "Enter your email address", "accountWelcomeBack": "Welcome back!", "email": "Email", diff --git a/mobile/lib/l10n/intl_tr.arb b/mobile/lib/l10n/intl_tr.arb new file mode 100644 index 0000000000..fab7542695 --- /dev/null +++ b/mobile/lib/l10n/intl_tr.arb @@ -0,0 +1,1279 @@ +{ + "enterYourEmailAddress": "E-posta adresinizi girin", + "accountWelcomeBack": "Tekrar hoş geldiniz!", + "email": "E-Posta", + "cancel": "İptal Et", + "verify": "Doğrula", + "invalidEmailAddress": "Geçersiz e-posta adresi", + "enterValidEmail": "Lütfen geçerli bir E-posta adresi girin.", + "deleteAccount": "Hesabı sil", + "askDeleteReason": "Hesabınızı neden silmek istiyorsunuz?", + "deleteAccountFeedbackPrompt": "Aramızdan ayrıldığınız için üzgünüz. Lütfen kendimizi geliştirmemize yardımcı olun. Neden ayrıldığınızı Açıklar mısınız.", + "feedback": "Geri Bildirim", + "kindlyHelpUsWithThisInformation": "Lütfen bu bilgilerle bize yardımcı olun", + "confirmDeletePrompt": "Evet, bu hesabı ve tüm verileri kalıcı olarak silmek istiyorum.", + "confirmAccountDeletion": "Hesap silme işlemini onayla", + "deleteAccountPermanentlyButton": "Hesabımı kalıcı olarak sil", + "yourAccountHasBeenDeleted": "Hesabınız silindi", + "selectReason": "Ayrılma nedeninizi seçin", + "deleteReason1": "İhtiyacım olan önemli bir özellik eksik", + "deleteReason2": "Uygulama veya bir özellik olması gerektiğini düşündüğüm gibi çalışmıyor", + "deleteReason3": "Daha çok sevdiğim başka bir hizmet buldum", + "deleteReason4": "Nedenim listede yok", + "sendEmail": "E-posta gönder", + "deleteRequestSLAText": "İsteğiniz 72 saat içinde gerçekleştirilecek.", + "deleteEmailRequest": "Lütfen kayıtlı e-posta adresinizden account-deletion@ente.io'a e-posta gönderiniz.", + "entePhotosPerm": "Ente needs permission to preserve your photos", + "ok": "Tamam", + "createAccount": "Hesap oluşturun", + "createNewAccount": "Yeni bir hesap oluşturun", + "password": "Şifre", + "confirmPassword": "Şifrenizi onaylayın", + "activeSessions": "Aktif oturumlar", + "oops": "Hay aksi", + "somethingWentWrongPleaseTryAgain": "Bir şeyler ters gitti, lütfen tekrar deneyin", + "thisWillLogYouOutOfThisDevice": "Bu cihazdaki oturumunuz kapatılacak!", + "thisWillLogYouOutOfTheFollowingDevice": "Bu, sizi aşağıdaki cihazdan çıkış yapacak:", + "terminateSession": "Oturumu sonlandır?", + "terminate": "Sonlandır", + "thisDevice": "Bu cihaz", + "recoverButton": "Kurtar", + "recoverySuccessful": "Kurtarma başarılı!", + "decrypting": "Şifre çözülüyor...", + "incorrectRecoveryKeyTitle": "Yanlış kurtarma kodu", + "incorrectRecoveryKeyBody": "Girdiğiniz kurtarma kod yanlış", + "forgotPassword": "Şifremi unuttum", + "enterYourRecoveryKey": "Kurtarma kodunuzu girin", + "noRecoveryKey": "Kurtarma kodunuz yok mu?", + "sorry": "Üzgünüz", + "noRecoveryKeyNoDecryption": "Uçtan uca şifreleme protokolümüzün doğası gereği, verileriniz şifreniz veya kurtarma anahtarınız olmadan çözülemez", + "verifyEmail": "E-posta adresini doğrulayın", + "toResetVerifyEmail": "Şifrenizi sıfılamak için lütfen e-postanızı girin.", + "checkInboxAndSpamFolder": "Lütfen doğrulama işlemini tamamlamak için gelen kutunuzu (ve spam klasörünüzü) kontrol edin", + "tapToEnterCode": "Kodu girmek icin tıklayın", + "resendEmail": "E-postayı yeniden gönder", + "weHaveSendEmailTo": "E-postayı {email} adresine gönderdik", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "setPasswordTitle": "Parola ayarlayın", + "changePasswordTitle": "Parolanızı değiştirin", + "resetPasswordTitle": "Parolanızı sıfırlayın", + "encryptionKeys": "Sifreleme anahtarı", + "passwordWarning": "Şifrelerinizi saklamıyoruz, bu yüzden unutursanız, verilerinizi deşifre edemeyiz", + "enterPasswordToEncrypt": "Verilerinizi şifrelemek için kullanabileceğimiz bir şifre girin", + "enterNewPasswordToEncrypt": "Verilerinizi şifrelemek için kullanabileceğimiz yeni bir şifre girin", + "weakStrength": "Zayıf", + "strongStrength": "Güçlü", + "moderateStrength": "Ilımlı", + "passwordStrength": "Şifrenin güçlülük seviyesi: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + }, + "message": "Password Strength: {passwordStrengthText}" + }, + "passwordChangedSuccessfully": "Şifreniz başarılı bir şekilde değiştirildi", + "generatingEncryptionKeys": "Şifreleme anahtarı oluşturuluyor...", + "pleaseWait": "Lütfen bekleyiniz...", + "continueLabel": "Devam edin", + "insecureDevice": "Güvenilir olmayan cihaz", + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Üzgünüm, bu cihazda güvenli anahtarlarını oluşturamadık.\n\nLütfen başka bir cihazdan giriş yapmayı deneyiniz.", + "howItWorks": "Nasıl çalışır", + "encryption": "Şifreleme", + "ackPasswordLostWarning": "Şifremi kaybedersem, verilerim uçtan uca şifrelendiği için verilerimi kaybedebileceğimi farkındayım.", + "privacyPolicyTitle": "Mahremiyet Politikası", + "termsOfServicesTitle": "Şartlar", + "signUpTerms": "Hizmet Şartları'nı ve Gizlilik Politikası'nı kabul ediyorum", + "logInLabel": "Giriş yap", + "loginTerms": "\"Giriş yap\" düğmesine tıklayarak, Hizmet Şartları'nı ve Gizlilik Politikası'nı kabul ediyorum", + "changeEmail": "E-posta adresini değiştir", + "enterYourPassword": "Lütfen şifrenizi giriniz", + "welcomeBack": "Tekrardan hoşgeldin!", + "contactSupport": "Destek ile iletişim", + "incorrectPasswordTitle": "Yanlış şifre", + "pleaseTryAgain": "Lütfen tekrar deneyiniz", + "recreatePasswordTitle": "Sifrenizi tekrardan oluşturun", + "useRecoveryKey": "Kurtarma anahtarını kullan", + "recreatePasswordBody": "Cihazınız, şifrenizi doğrulamak için yeterli güce sahip değil, ancak tüm cihazlarda çalışacak şekilde yeniden oluşturabiliriz.\n\nLütfen kurtarma anahtarınızı kullanarak giriş yapın ve şifrenizi yeniden oluşturun (istediğiniz takdirde aynı şifreyi tekrar kullanabilirsiniz).", + "verifyPassword": "Şifrenizi doğrulayın", + "recoveryKey": "Kurtarma anahtarı", + "recoveryKeyOnForgotPassword": "Şifrenizi unutursanız, verilerinizi kurtarmanın tek yolu bu anahtar olacaktır.", + "recoveryKeySaveDescription": "Bu anahtarı saklamıyoruz, lütfen bu 24 kelime anahtarı güvenli bir yerde saklayın.", + "doThisLater": "Sonra yap", + "saveKey": "Anahtarı kaydet", + "recoveryKeyCopiedToClipboard": "Kurtarma anahtarınız panoya kopyalandı", + "recoverAccount": "Hesabı kurtar", + "recover": "Kurtarma", + "dropSupportEmail": "Lütfen kayıtlı e-posta adresinizden {supportEmail} adresine bir e-posta gönderin", + "@dropSupportEmail": { + "placeholders": { + "supportEmail": { + "description": "The support email address", + "type": "String", + "example": "support@ente.io" + } + } + }, + "twofactorSetup": "Cift faktör ayarı", + "enterCode": "Kodu giriniz", + "scanCode": "Kodu tarayın", + "codeCopiedToClipboard": "Kodunuz panoya kopyalandı", + "copypasteThisCodentoYourAuthenticatorApp": "Bu kodu kopyalayın ve kimlik doğrulama uygulamanıza yapıştırın", + "tapToCopy": "kopyalamak için dokunun", + "scanThisBarcodeWithnyourAuthenticatorApp": "Kimlik doğrulama uygulamanız ile kodu tarayın", + "enterThe6digitCodeFromnyourAuthenticatorApp": "Doğrulama uygulamasındaki 6 basamaklı kodu giriniz", + "confirm": "Onayla", + "setupComplete": "Ayarlama işlemi başarılı", + "saveYourRecoveryKeyIfYouHaventAlready": "Henüz yapmadıysanız kurtarma anahtarınızı kaydetmeyi unutmayın", + "thisCanBeUsedToRecoverYourAccountIfYou": "Bu, iki faktörünüzü kaybederseniz hesabınızı kurtarmak için kullanılabilir", + "twofactorAuthenticationPageTitle": "İki faktörlü doğrulama", + "lostDevice": "Cihazı kayıp mı ettiniz?", + "verifyingRecoveryKey": "Kurtarma kodu doğrulanıyor...", + "recoveryKeyVerified": "Kurtarma kodu doğrulandı", + "recoveryKeySuccessBody": "Harika! Kurtarma anahtarınız geçerlidir. Doğrulama için teşekkür ederim.\n\nLütfen kurtarma anahtarınızı güvenli bir şekilde yedeklediğinizden emin olun.", + "invalidRecoveryKey": "Girdiğiniz kurtarma anahtarı geçerli değil. Lütfen anahtarın 24 kelime içerdiğinden ve her bir kelimenin doğru şekilde yazıldığından emin olun.\n\nEğer eski bir kurtarma kodu girdiyseniz, o zaman kodun 64 karakter uzunluğunda olduğunu kontrol edin.", + "invalidKey": "Gecersiz anahtar", + "tryAgain": "Tekrar deneyiniz", + "viewRecoveryKey": "Kurtarma anahtarını görüntüle", + "confirmRecoveryKey": "Kurtarma anahtarını doğrula", + "recoveryKeyVerifyReason": "Eğer şifrenizi unutursanız, fotoğraflarınızı kurtarmanın tek yolu kurtarma anahtarınızdır. Kurtarma anahtarınızı Ayarlar > Güvenlik bölümünde bulabilirsiniz.\n\nLütfen kurtarma anahtarınızı buraya girerek doğru bir şekilde kaydettiğinizi doğrulayın.", + "confirmYourRecoveryKey": "Kurtarma anahtarını doğrulayın", + "addViewer": "Görüntüleyici ekle", + "addCollaborator": "Düzenleyici ekle", + "addANewEmail": "Yeni e-posta ekle", + "orPickAnExistingOne": "Veya mevcut birini seçiniz", + "collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": "Düzenleyiciler, paylaşılan albüme fotoğraf ve videolar ekleyebilir.", + "enterEmail": "E-postanızı giriniz", + "albumOwner": "Sahip", + "@albumOwner": { + "description": "Role of the album owner" + }, + "you": "Sen", + "collaborator": "Düzenleyici", + "addMore": "Daha fazla ekle", + "@addMore": { + "description": "Button text to add more collaborators/viewers" + }, + "viewer": "Görüntüleyici", + "remove": "Kaldır", + "removeParticipant": "Katılımcıyı kaldır", + "@removeParticipant": { + "description": "menuSectionTitle for removing a participant" + }, + "manage": "Yönet", + "addedAs": "Eklendi", + "changePermissions": "İzinleri değiştir?", + "yesConvertToViewer": "Evet, görüntüleyici olarak dönüştür", + "cannotAddMorePhotosAfterBecomingViewer": "{user}, bu albüme daha fazla fotoğraf ekleyemeyecek.\n\nAncak, kendi eklediği mevcut fotoğrafları kaldırmaya devam edebilecektir", + "allowAddingPhotos": "Fotoğraf eklemeye izin ver", + "@allowAddingPhotos": { + "description": "Switch button to enable uploading photos to a public link" + }, + "allowAddPhotosDescription": "Bağlantıya sahip olan kişilere, paylaşılan albüme fotoğraf eklemelerine izin ver.", + "passwordLock": "Sifre kilidi", + "disableDownloadWarningTitle": "Lütfen dikkate alın", + "disableDownloadWarningBody": "Görüntüleyiciler, hala harici araçlar kullanarak ekran görüntüsü alabilir veya fotoğraflarınızın bir kopyasını kaydedebilir. Lütfen bunu göz önünde bulundurunuz", + "allowDownloads": "İndirmeye izin ver", + "linkDeviceLimit": "Cihaz limiti", + "noDeviceLimit": "Yok", + "@noDeviceLimit": { + "description": "Text to indicate that there is limit on number of devices" + }, + "linkExpiry": "Linkin geçerliliği", + "linkExpired": "Süresi dolmuş", + "linkEnabled": "Geçerli", + "linkNeverExpires": "Asla", + "expiredLinkInfo": "Bu bağlantının süresi dolmuştur. Lütfen yeni bir süre belirleyin veya bağlantı süresini devre dışı bırakın.", + "setAPassword": "Şifre ayarla", + "lockButtonLabel": "Kilit", + "enterPassword": "Şifrenizi girin", + "removeLink": "Linki kaldır", + "manageLink": "Linki yönet", + "linkExpiresOn": "Bu bağlantı {expiryTime} dan sonra geçersiz olacaktır", + "albumUpdated": "Albüm güncellendi", + "never": "Asla", + "custom": "Kişisel", + "@custom": { + "description": "Label for setting custom value for link expiry" + }, + "after1Hour": "1 saat sonra", + "after1Day": "1 gün sonra", + "after1Week": "1 hafta sonra", + "after1Month": "1 ay sonra", + "after1Year": "1 yıl sonra", + "manageParticipants": "Yönet", + "albumParticipantsCount": "{count, plural, =0 {Katılımcı Yok} =1 {1 Katılımcı} other {{count} Katılımcı}}", + "@albumParticipantsCount": { + "placeholders": { + "count": { + "type": "int", + "example": "5" + } + }, + "description": "Number of participants in an album, including the album owner." + }, + "collabLinkSectionDescription": "Create a link to allow people to add and view photos in your shared album without needing an Ente app or account. Great for collecting event photos.", + "collectPhotos": "Fotoğrafları topla", + "collaborativeLink": "Organizasyon bağlantısı", + "shareWithNonenteUsers": "Share with non-Ente users", + "createPublicLink": "Herkese açık link oluştur", + "sendLink": "Link gönder", + "copyLink": "Linki kopyala", + "linkHasExpired": "Bağlantının süresi dolmuş", + "publicLinkEnabled": "Herkese açık bağlantı aktive edildi", + "shareALink": "Linki paylaş", + "sharedAlbumSectionDescription": "Create shared and collaborative albums with other Ente users, including users on free plans.", + "shareWithPeopleSectionTitle": "{numberOfPeople, plural, =0 {Belirli kişilerle paylaş} =1 {1 kişiyle paylaşıldı} other {{numberOfPeople} kişiyle paylaşıldı}}", + "@shareWithPeopleSectionTitle": { + "placeholders": { + "numberOfPeople": { + "type": "int", + "example": "2" + } + } + }, + "thisIsYourVerificationId": "Doğrulama kimliğiniz", + "someoneSharingAlbumsWithYouShouldSeeTheSameId": "Size albümleri paylaşan biri, kendi cihazında aynı kimliği görmelidir.", + "howToViewShareeVerificationID": "Lütfen onlardan ayarlar ekranında e-posta adresine uzun süre basmalarını ve her iki cihazdaki kimliklerin eşleştiğini doğrulamalarını isteyin.", + "thisIsPersonVerificationId": "Bu, {email}'in Doğrulama Kimliği", + "@thisIsPersonVerificationId": { + "placeholders": { + "email": { + "type": "String", + "example": "someone@ente.io" + } + } + }, + "verificationId": "Doğrulama kimliği", + "verifyEmailID": "{email} doğrula", + "emailNoEnteAccount": "{email} does not have an Ente account.\n\nSend them an invite to share photos.", + "shareMyVerificationID": "İşte ente.io için doğrulama kimliğim: {verificationID}.", + "shareTextConfirmOthersVerificationID": "Merhaba, bu ente.io doğrulama kimliğinizin doğruluğunu onaylayabilir misiniz: {verificationID}", + "somethingWentWrong": "Bazı şeyler yanlış gitti", + "sendInvite": "Davet kodu gönder", + "shareTextRecommendUsingEnte": "Download Ente so we can easily share original quality photos and videos\n\nhttps://ente.io", + "done": "Bitti", + "applyCodeTitle": "Kodu girin", + "enterCodeDescription": "Arkadaşınız tarafından sağlanan kodu girerek hem sizin hem de arkadaşınızın ücretsiz depolamayı talep etmek için girin", + "apply": "Uygula", + "failedToApplyCode": "Uygulanırken hata oluştu", + "enterReferralCode": "Davet kodunuzu girin", + "codeAppliedPageTitle": "Kod kabul edildi", + "storageInGB": "{storageAmountInGB} GB", + "claimed": "Alındı", + "@claimed": { + "description": "Used to indicate storage claimed, like 10GB Claimed" + }, + "details": "Ayrıntılar", + "claimMore": "Arttır!", + "theyAlsoGetXGb": "Aynı zamanda {storageAmountInGB} GB alıyorlar", + "freeStorageOnReferralSuccess": "Birisinin davet kodunuzu uygulayıp ücretli hesap açtığı her seferede {storageAmountInGB} GB", + "shareTextReferralCode": "Ente referral code: {referralCode} \n\nApply it in Settings → General → Referrals to get {referralStorageInGB} GB free after you signup for a paid plan\n\nhttps://ente.io", + "claimFreeStorage": "Bedava alan talep edin", + "inviteYourFriends": "Arkadaşlarını davet et", + "failedToFetchReferralDetails": "Davet ayrıntıları çekilemedi. Iütfen daha sonra deneyin.", + "referralStep1": "1. Bu kodu arkadaşlarınıza verin", + "referralStep2": "2. Ücretli bir plan için kaydolsunlar", + "referralStep3": "3. Hepimiz {storageInGB} GB* bedava alın", + "referralsAreCurrentlyPaused": "Davetler şu anda durmuş durumda", + "youCanAtMaxDoubleYourStorage": "* Alanınızı en fazla ikiye katlayabilirsiniz", + "claimedStorageSoFar": "{isFamilyMember, select, true {Şu ana kadar aileniz {storageAmountInGb} GB aldı} false {Şu ana kadar {storageAmountInGb} GB aldınız} other {Şu ana kadar {storageAmountInGb} GB aldınız!}}", + "@claimedStorageSoFar": { + "placeholders": { + "isFamilyMember": { + "type": "String", + "example": "true" + }, + "storageAmountInGb": { + "type": "int", + "example": "10" + } + } + }, + "faq": "Sıkça sorulan sorular", + "help": "Yardım", + "oopsSomethingWentWrong": "Hoop, Birşeyler yanlış gitti", + "peopleUsingYourCode": "Kodunuzu kullananlar", + "eligible": "uygun", + "total": "total", + "codeUsedByYou": "Sizin kullandığınız kod", + "freeStorageClaimed": "Alınan bedava alan", + "freeStorageUsable": "Kullanılabilir bedava alan", + "usableReferralStorageInfo": "Kullanılabilir depolama alanı mevcut planınızla sınırlıdır. Talep edilen fazla depolama alanı, planınızı yükselttiğinizde otomatik olarak kullanılabilir hale gelecektir.", + "removeFromAlbumTitle": "Albümden çıkarılsın mı?", + "removeFromAlbum": "Albümden çıkar", + "itemsWillBeRemovedFromAlbum": "Seçilen öğeler bu albümden kaldırılacak", + "removeShareItemsWarning": "Kaldırdığınız öğelerden bazıları başkaları tarafından eklenmiştir ve bunlara erişiminizi kaybedeceksiniz", + "addingToFavorites": "Favorilere ekleniyor...", + "removingFromFavorites": "Favorilerimden kaldır...", + "sorryCouldNotAddToFavorites": "Üzgünüm, favorilere ekleyemedim!", + "sorryCouldNotRemoveFromFavorites": "Üzgünüm, favorilere ekleyemedim!", + "subscribeToEnableSharing": "Aboneliğinizin süresi dolmuş gibi görünüyor. Paylaşımı etkinleştirmek için lütfen abone olun.", + "subscribe": "Abone ol", + "canOnlyRemoveFilesOwnedByYou": "Yalnızca size ait dosyaları kaldırabilir", + "deleteSharedAlbum": "Paylaşılan albüm silinsin mi?", + "deleteAlbum": "Albümü sil", + "deleteAlbumDialog": "Ayrıca bu albümde bulunan fotoğrafları (ve videoları) parçası oldukları tüm diğer albümlerden silebilir miyim?", + "deleteSharedAlbumDialogBody": "Albüm herkes için silinecek\n\nBu albümdeki başkalarına ait paylaşılan fotoğraflara erişiminizi kaybedeceksiniz", + "yesRemove": "Evet, sil", + "creatingLink": "Bağlantı oluşturuluyor...", + "removeWithQuestionMark": "Kaldır?", + "removeParticipantBody": "{userEmail} bu paylaşılan albümden kaldırılacaktır\n\nOnlar tarafından eklenen tüm fotoğraflar da albümden kaldırılacaktır", + "keepPhotos": "Fotoğrafları sakla", + "deletePhotos": "Fotoğrafları sil", + "inviteToEnte": "Invite to Ente", + "removePublicLink": "Herkese açık link oluştur", + "disableLinkMessage": "Bu, \"{albumName}\"e erişim için olan genel bağlantıyı kaldıracaktır.", + "sharing": "Paylaşılıyor...", + "youCannotShareWithYourself": "Kendinizle paylaşamazsınız", + "archive": "Arşiv", + "createAlbumActionHint": "Fotoğrafları seçmek için uzun basın ve + düğmesine tıklayarak bir albüm oluşturun", + "importing": "İçeri aktarılıyor....", + "failedToLoadAlbums": "Albüm yüklenirken hata oluştu", + "hidden": "Gizle", + "authToViewYourHiddenFiles": "Gizli dosyalarınızı görüntülemek için kimlik doğrulama yapınız", + "trash": "Cöp kutusu", + "uncategorized": "Kategorisiz", + "videoSmallCase": "video", + "photoSmallCase": "fotoğraf", + "singleFileDeleteHighlight": "Tüm albümlerden silinecek.", + "singleFileInBothLocalAndRemote": "This {fileType} is in both Ente and your device.", + "singleFileInRemoteOnly": "This {fileType} will be deleted from Ente.", + "singleFileDeleteFromDevice": "Bu {fileType}, cihazınızdan silinecek.", + "deleteFromEnte": "Delete from Ente", + "yesDelete": "Evet, sil", + "movedToTrash": "Cöp kutusuna taşı", + "deleteFromDevice": "Cihazınızdan silin", + "deleteFromBoth": "Her ikisinden de sil", + "newAlbum": "Yeni albüm", + "albums": "Albümler", + "memoryCount": "{count, plural, zero{anı yok} \none{{formattedCount} anı} \nother{{formattedCount} anılar}}", + "@memoryCount": { + "description": "The text to display the number of memories", + "type": "text", + "placeholders": { + "count": { + "example": "1", + "type": "int" + }, + "formattedCount": { + "type": "String", + "example": "11.513, 11,511" + } + } + }, + "selectedPhotos": "{count} seçildi", + "@selectedPhotos": { + "description": "Display the number of selected photos", + "type": "text", + "placeholders": { + "count": { + "example": "5", + "type": "int" + } + } + }, + "selectedPhotosWithYours": "Seçilenler: {count} ({yourCount} sizin seçiminiz)", + "@selectedPhotosWithYours": { + "description": "Display the number of selected photos, including the number of selected photos owned by the user", + "type": "text", + "placeholders": { + "count": { + "example": "12", + "type": "int" + }, + "yourCount": { + "example": "2", + "type": "int" + } + } + }, + "advancedSettings": "Gelişmiş", + "@advancedSettings": { + "description": "The text to display in the advanced settings section" + }, + "photoGridSize": "Fotoğraf ızgara boyutu", + "manageDeviceStorage": "Cihaz depolamasını yönet", + "machineLearning": "Makine öğrenimi", + "magicSearch": "Sihirli arama", + "mlIndexingDescription": "Please note that machine learning will result in a higher bandwidth and battery usage until all items are indexed.", + "loadingModel": "Modeller indiriliyor...", + "waitingForWifi": "WiFi bekleniyor...", + "status": "Durum", + "indexedItems": "Yeni öğeleri indeksle", + "pendingItems": "Bekleyen Öğeler", + "clearIndexes": "Açık Dizin", + "selectFoldersForBackup": "Yedekleme için klasörleri seçin", + "selectedFoldersWillBeEncryptedAndBackedUp": "Seçilen klasörler şifrelenecek ve yedeklenecektir", + "unselectAll": "Tümünün seçimini kaldır", + "selectAll": "Hepsini seç", + "skip": "Geç", + "updatingFolderSelection": "Klasör seçimi güncelleniyor...", + "itemCount": "{count,plural, one{{count} öğe} other{{count} öğeler}}", + "deleteItemCount": "{count, plural, =1 {Delete {count} item} other {Delete {count} items}}", + "duplicateItemsGroup": "{count} dosyalar, {formattedSize} her biri", + "@duplicateItemsGroup": { + "description": "Display the number of duplicate files and their size", + "type": "text", + "placeholders": { + "count": { + "example": "12", + "type": "int" + }, + "formattedSize": { + "example": "2.3 MB", + "type": "String" + } + } + }, + "showMemories": "Anıları göster", + "yearsAgo": "{count, plural, one{{count} yıl önce} other{{count} yıl önce}}", + "backupSettings": "Yedekleme seçenekleri", + "backupOverMobileData": "Mobil veri ile yedekle", + "backupVideos": "Videolari yedekle", + "disableAutoLock": "Otomatik kilidi devre dışı bırak", + "deviceLockExplanation": "Disable the device screen lock when Ente is in the foreground and there is a backup in progress. This is normally not needed, but may help big uploads and initial imports of large libraries complete faster.", + "about": "Hakkında", + "weAreOpenSource": "Biz açık kaynağız!", + "privacy": "Gizlilik", + "terms": "Şartlar", + "checkForUpdates": "Güncellemeleri kontol et", + "checkStatus": "Check status", + "checking": "Kontrol ediliyor...", + "youAreOnTheLatestVersion": "En son sürüme sahipsiniz", + "account": "Hesap", + "manageSubscription": "Abonelikleri yönet", + "authToChangeYourEmail": "E-postanızı değiştirmek için lütfen kimlik doğrulaması yapın", + "changePassword": "Sifrenizi değiştirin", + "authToChangeYourPassword": "Şifrenizi değiştirmek için lütfen kimlik doğrulaması yapın", + "emailVerificationToggle": "E-posta doğrulama", + "authToChangeEmailVerificationSetting": "E-posta doğrulamasını değiştirmek için lütfen kimlik doğrulaması yapın", + "exportYourData": "Veriyi dışarı aktar", + "logout": "Çıkış yap", + "authToInitiateAccountDeletion": "Hesap silme işlemini başlatmak için lütfen kimlik doğrulaması yapın", + "areYouSureYouWantToLogout": "Çıkış yapmak istediğinize emin misiniz?", + "yesLogout": "Evet, oturumu kapat", + "aNewVersionOfEnteIsAvailable": "A new version of Ente is available.", + "update": "Güncelle", + "installManually": "Manuel kurulum", + "criticalUpdateAvailable": "Kritik güncelleme mevcut", + "updateAvailable": "Güncelleme mevcut", + "ignoreUpdate": "Yoksay", + "downloading": "İndiriliyor...", + "cannotDeleteSharedFiles": "Dosyalar silinemiyor", + "theDownloadCouldNotBeCompleted": "İndirme işlemi tamamlanamadı", + "retry": "Tekrar dene", + "backedUpFolders": "Yedeklenmiş klasörler", + "backup": "Yedekle", + "freeUpDeviceSpace": "Cihaz alanını boşaltın", + "freeUpDeviceSpaceDesc": "Save space on your device by clearing files that have been already backed up.", + "allClear": "✨ Tamamen temizle", + "noDeviceThatCanBeDeleted": "Bu cihazda silinebilecek hiçbir dosyanız yok", + "removeDuplicates": "Yinelenenleri kaldır", + "removeDuplicatesDesc": "Review and remove files that are exact duplicates.", + "viewLargeFiles": "Large files", + "viewLargeFilesDesc": "View files that are consuming the most amount of storage", + "noDuplicates": "Yinelenenleri kaldır", + "youveNoDuplicateFilesThatCanBeCleared": "Temizlenebilecek yinelenen dosyalarınız yok", + "success": "Başarılı", + "rateUs": "Bizi değerlendirin", + "remindToEmptyDeviceTrash": "Ayrıca boşalan alanı talep etmek için \"Ayarlar\" -> \"Depolama\" bölümünden \"Son Silinenler \"i boşaltın", + "youHaveSuccessfullyFreedUp": "Başarılı bir şekilde {storageSaved} alanını boşalttınız!", + "@youHaveSuccessfullyFreedUp": { + "description": "The text to display when the user has successfully freed up storage", + "type": "text", + "placeholders": { + "storageSaved": { + "example": "1.2 GB", + "type": "String" + } + } + }, + "remindToEmptyEnteTrash": "Ayrıca boşalan alana sahip olmak için \"Çöp Kutunuzu\" boşaltın", + "sparkleSuccess": "✨ Başarılı", + "duplicateFileCountWithStorageSaved": "You have cleaned up {count, plural, one{{count} duplicate file} other{{count} duplicate files}}, saving ({storageSaved}!)", + "@duplicateFileCountWithStorageSaved": { + "description": "The text to display when the user has successfully cleaned up duplicate files", + "type": "text", + "placeholders": { + "count": { + "example": "1", + "type": "int" + }, + "storageSaved": { + "example": "1.2 GB", + "type": "String" + } + } + }, + "familyPlans": "Aile Planı", + "referrals": "Referanslar", + "notifications": "Bildirimler", + "sharedPhotoNotifications": "Paylaşılan fotoğrafları ekle", + "sharedPhotoNotificationsExplanation": "Birisi sizin de parçası olduğunuz paylaşılan bir albüme fotoğraf eklediğinde bildirim alın", + "advanced": "Gelişmiş", + "general": "Genel", + "security": "Güvenlik", + "authToViewYourRecoveryKey": "Kurtarma anahtarınızı görmek için lütfen kimliğinizi doğrulayın", + "twofactor": "İki faktör", + "authToConfigureTwofactorAuthentication": "İki faktörlü kimlik doğrulamayı yapılandırmak için lütfen kimlik doğrulaması yapın", + "lockscreen": "Kilit ekranı", + "authToChangeLockscreenSetting": "Kilit ekranı ayarını değiştirmek için lütfen kimliğinizi doğrulayın", + "lockScreenEnablePreSteps": "Kilit ekranını aktif etmek için lütfen cihazın ayarlarından şifreyi ya da ekran kilidini ayarlayın.", + "viewActiveSessions": "Aktif oturumları görüntüle", + "authToViewYourActiveSessions": "Aktif oturumlarınızı görüntülemek için lütfen kimliğinizi doğrulayın", + "disableTwofactor": "İki Aşamalı Doğrulamayı Devre Dışı Bırak", + "confirm2FADisable": "İki adımlı kimlik doğrulamasını devre dışı bırakmak istediğinize emin misiniz?", + "no": "Hayır", + "yes": "Evet", + "social": "Sosyal Medya", + "rateUsOnStore": "Bizi {storeName} üzerinden değerlendirin", + "blog": "Blog", + "merchandise": "Ürünler", + "twitter": "Twitter", + "mastodon": "Mastodon", + "matrix": "Matrix", + "discord": "Discord", + "reddit": "Reddit", + "yourStorageDetailsCouldNotBeFetched": "Depolama bilgisi alınamadı", + "reportABug": "Hatayı bildir", + "reportBug": "Hata bildir", + "suggestFeatures": "Özellik önerin", + "support": "Destek", + "theme": "Tema", + "lightTheme": "Aydınlık", + "darkTheme": "Karanlık", + "systemTheme": "Sistem", + "freeTrial": "Ücretsiz deneme", + "selectYourPlan": "Planınızı seçin", + "enteSubscriptionPitch": "Ente preserves your memories, so they're always available to you, even if you lose your device.", + "enteSubscriptionShareWithFamily": "Aileniz de planınıza eklenebilir.", + "currentUsageIs": "Güncel kullanımınız ", + "@currentUsageIs": { + "description": "This text is followed by storage usage", + "examples": { + "0": "Current usage is 1.2 GB" + }, + "type": "text" + }, + "faqs": "Sık sorulanlar", + "renewsOn": "Abonelik {endDate} tarihinde yenilenir", + "freeTrialValidTill": "Ücretsiz deneme {endDate} sona erir", + "validTill": "{endDate} tarihine kadar geçerli", + "addOnValidTill": "{storageAmount} eklentiniz {endDate} tarihine kadar geçerlidir", + "playStoreFreeTrialValidTill": "Free trial valid till {endDate}.\nYou can choose a paid plan afterwards.", + "subWillBeCancelledOn": "Aboneliğiniz {endDate} tarihinde iptal edilecektir", + "subscription": "Abonelik", + "paymentDetails": "Ödeme detayları", + "manageFamily": "Aileyi yönet", + "contactToManageSubscription": "Lütfen {provider} aboneliğinizi yönetmek için support@ente.io adresinden bizimle iletişime geçin.", + "renewSubscription": "Abonelik yenileme", + "cancelSubscription": "Abonelik iptali", + "areYouSureYouWantToRenew": "Yenilemek istediğinize emin misiniz?", + "yesRenew": "Evet, yenile", + "areYouSureYouWantToCancel": "İptal etmek istediğinize emin misiniz?", + "yesCancel": "Evet, iptal et", + "failedToRenew": "Abonelik yenilenirken hata oluştu", + "failedToCancel": "İptal edilirken sorun oluştu", + "twoMonthsFreeOnYearlyPlans": "Yıllık planlarda 2 ay ücretsiz", + "monthly": "Aylık", + "@monthly": { + "description": "The text to display for monthly plans", + "type": "text" + }, + "yearly": "Yıllık", + "@yearly": { + "description": "The text to display for yearly plans", + "type": "text" + }, + "confirmPlanChange": "Plan değişikliğini onaylayın", + "areYouSureYouWantToChangeYourPlan": "Planı değistirmek istediğinize emin misiniz?", + "youCannotDowngradeToThisPlan": "Bu plana geçemezsiniz", + "cancelOtherSubscription": "Lütfen önce mevcut aboneliğinizi {paymentProvider} adresinden iptal edin", + "@cancelOtherSubscription": { + "description": "The text to display when the user has an existing subscription from a different payment provider", + "type": "text", + "placeholders": { + "paymentProvider": { + "example": "Apple", + "type": "String" + } + } + }, + "optionalAsShortAsYouLike": "İsteğe bağlı, istediğiniz kadar kısa...", + "send": "Gönder", + "askCancelReason": "Aboneliğiniz iptal edilmiştir. Bunun sebebini paylaşmak ister misiniz?", + "thankYouForSubscribing": "Abone olduğunuz için teşekkürler!", + "yourPurchaseWasSuccessful": "Satın alım başarılı", + "yourPlanWasSuccessfullyUpgraded": "Planınız başarılı şekilde yükseltildi", + "yourPlanWasSuccessfullyDowngraded": "Planınız başarıyla düşürüldü", + "yourSubscriptionWasUpdatedSuccessfully": "Aboneliğiniz başarıyla güncellendi", + "googlePlayId": "Google play kimliği", + "appleId": "Apple kimliği", + "playstoreSubscription": "PlayStore aboneliği", + "appstoreSubscription": "PlayStore aboneliği", + "subAlreadyLinkedErrMessage": "Your {id} is already linked to another Ente account.\nIf you would like to use your {id} with this account, please contact our support''", + "visitWebToManage": "Aboneliğinizi yönetmek için lütfen web.ente.io adresini ziyaret edin", + "couldNotUpdateSubscription": "Abonelikler kaydedilemedi", + "pleaseContactSupportAndWeWillBeHappyToHelp": "Lütfen support@ente.io ile iletişime geçin; size yardımcı olmaktan memnuniyet duyarız!", + "paymentFailed": "Ödeme başarısız oldu", + "paymentFailedTalkToProvider": "Sizden ücret alındıysa lütfen {providerName} destek ekibiyle görüşün", + "@paymentFailedTalkToProvider": { + "description": "The text to display when the payment failed", + "type": "text", + "placeholders": { + "providerName": { + "example": "AppStore|PlayStore", + "type": "String" + } + } + }, + "continueOnFreeTrial": "Ücretsiz denemeye devam et", + "areYouSureYouWantToExit": "Çıkmak istediğinden emin misin?", + "thankYou": "Teşekkürler", + "failedToVerifyPaymentStatus": "Ödeme durumu doğrulanamadı", + "pleaseWaitForSometimeBeforeRetrying": "Tekrar denemeden önce lütfen bir süre bekleyin", + "paymentFailedMessage": "Maalesef ödemeniz başarısız oldu. Lütfen destekle iletişime geçin, size yardımcı olacağız!", + "youAreOnAFamilyPlan": "Aile planı kullanıyorsunuz!", + "contactFamilyAdmin": "Aboneliğinizi yönetmek için lütfen {familyAdminEmail} ile iletişime geçin", + "leaveFamily": "Aile planından ayrıl", + "areYouSureThatYouWantToLeaveTheFamily": "Aile planından ayrılmak istediğinize emin misiniz?", + "leave": "Çıkış yap", + "rateTheApp": "Uygulamaya puan verin", + "startBackup": "Yedeklemeyi başlat", + "noPhotosAreBeingBackedUpRightNow": "Şu anda hiçbir fotoğraf yedeklenmiyor", + "preserveMore": "Daha fazlasını koruyun", + "grantFullAccessPrompt": "Lütfen Ayarlar uygulamasında tüm fotoğraflara erişime izin verin", + "openSettings": "Ayarları Açın", + "selectMorePhotos": "Daha Fazla Fotoğraf Seç", + "existingUser": "Mevcut kullanıcı", + "privateBackups": "Özel yedeklemeler", + "forYourMemories": "anıların için", + "endtoendEncryptedByDefault": "Varsayılan olarak uçtan uca şifrelenmiş", + "safelyStored": "Güvenle saklanır", + "atAFalloutShelter": "serpinti sığınağında", + "designedToOutlive": "Hayatta kalmak için tasarlandı", + "available": "Mevcut", + "everywhere": "her yerde", + "androidIosWebDesktop": "Android, iOS, Web, Masaüstü", + "mobileWebDesktop": "Mobil, Web, Masaüstü", + "newToEnte": "New to Ente", + "pleaseLoginAgain": "Lütfen tekrar giriş yapın", + "autoLogoutMessage": "Due to technical glitch, you have been logged out. Our apologies for the inconvenience.", + "yourSubscriptionHasExpired": "Aboneliğinizin süresi doldu", + "storageLimitExceeded": "Depolama sınırı aşıldı", + "upgrade": "Yükselt", + "raiseTicket": "Bileti artır", + "@raiseTicket": { + "description": "Button text for raising a support tickets in case of unhandled errors during backup", + "type": "text" + }, + "backupFailed": "Yedekleme başarısız oldu", + "couldNotBackUpTryLater": "Verilerinizi yedekleyemedik.\nDaha sonra tekrar deneyeceğiz.", + "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "Ente can encrypt and preserve files only if you grant access to them", + "pleaseGrantPermissions": "Lütfen izin ver", + "grantPermission": "İzinleri değiştir", + "privateSharing": "Özel paylaşım", + "shareOnlyWithThePeopleYouWant": "Yalnızca istediğiniz kişilerle paylaşın", + "usePublicLinksForPeopleNotOnEnte": "Use public links for people not on Ente", + "allowPeopleToAddPhotos": "Kullanıcıların fotoğraf eklemesine izin ver", + "shareAnAlbumNow": "Şimdi bir albüm paylaşın", + "collectEventPhotos": "Etkinlik fotoğraflarını topla", + "sessionExpired": "Oturum süresi doldu", + "loggingOut": "Çıkış yapılıyor...", + "@onDevice": { + "description": "The text displayed above folders/albums stored on device", + "type": "text" + }, + "onDevice": "Bu cihaz", + "@onEnte": { + "description": "The text displayed above albums backed up to Ente", + "type": "text" + }, + "onEnte": "ente üzerinde", + "name": "İsim", + "newest": "En yeni", + "lastUpdated": "En son güncellenen", + "deleteEmptyAlbums": "Boş albümleri sil", + "deleteEmptyAlbumsWithQuestionMark": "Boş albümleri sileyim mi?", + "deleteAlbumsDialogBody": "Bu, tüm boş albümleri silecektir. Bu, albüm listenizdeki dağınıklığı azaltmak istediğinizde kullanışlıdır.", + "deleteProgress": "Siliniyor {currentlyDeleting} / {totalCount}", + "genericProgress": "Siliniyor {currentlyProcessing} / {totalCount}", + "@genericProgress": { + "description": "Generic progress text to display when processing multiple items", + "type": "text", + "placeholders": { + "currentlyProcessing": { + "example": "1", + "type": "int" + }, + "totalCount": { + "example": "10", + "type": "int" + } + } + }, + "permanentlyDelete": "Kalıcı olarak sil", + "canOnlyCreateLinkForFilesOwnedByYou": "Yalnızca size ait dosyalar için bağlantı oluşturabilir", + "publicLinkCreated": "Herkese açık link oluşturuldu", + "youCanManageYourLinksInTheShareTab": "Bağlantılarınızı paylaşım sekmesinden yönetebilirsiniz.", + "linkCopiedToClipboard": "Link panoya kopyalandı", + "restore": "Yenile", + "@restore": { + "description": "Display text for an action which triggers a restore of item from trash", + "type": "text" + }, + "moveToAlbum": "Albüme taşı", + "unhide": "Gizleme", + "unarchive": "Arşivden cıkar", + "favorite": "Favori", + "removeFromFavorite": "Favorilerimden kaldır", + "shareLink": "Linki paylaş", + "createCollage": "Kolaj oluştur", + "saveCollage": "Kolajı kaydet", + "collageSaved": "Kolajınız galeriye kaydedildi", + "collageLayout": "Düzen", + "addToEnte": "Add to Ente", + "addToAlbum": "Albüme ekle", + "delete": "Sil", + "hide": "Gizle", + "share": "Paylaş", + "unhideToAlbum": "Albümü gizleme", + "restoreToAlbum": "Albümü yenile", + "moveItem": "{count, plural, one {Öğeyi taşı} other {Öğeleri taşı}}", + "@moveItem": { + "description": "Page title while moving one or more items to an album" + }, + "addItem": "{count, plural, one {Öğeyi taşı} other {Öğeleri taşı}}", + "@addItem": { + "description": "Page title while adding one or more items to album" + }, + "createOrSelectAlbum": "Albüm oluştur veya seç", + "selectAlbum": "Albüm seçin", + "searchByAlbumNameHint": "Albüm adı", + "albumTitle": "Albüm Başlığı", + "enterAlbumName": "Bir albüm adı girin", + "restoringFiles": "Dosyalar geri yükleniyor...", + "movingFilesToAlbum": "Dosyalar albüme taşınıyor...", + "unhidingFilesToAlbum": "Albümdeki dosyalar gösteriliyor", + "canNotUploadToAlbumsOwnedByOthers": "Başkalarına ait albümlere yüklenemez", + "uploadingFilesToAlbum": "Dosyalar albüme taşınıyor...", + "addedSuccessfullyTo": "{albumName} albümüne başarıyla eklendi", + "movedSuccessfullyTo": "{albumName} adlı albüme başarıyla taşındı", + "thisAlbumAlreadyHDACollaborativeLink": "Bu albümde zaten bir ortak çalışma bağlantısı var", + "collaborativeLinkCreatedFor": "{albumName} için ortak çalışma bağlantısı oluşturuldu", + "askYourLovedOnesToShare": "Sevdiklerinizden paylaşmalarını isteyin", + "invite": "Davet et", + "shareYourFirstAlbum": "İlk albümünüzü paylaşın", + "sharedWith": "{emailIDs} ile paylaşıldı", + "sharedWithMe": "Benimle paylaşılan", + "sharedByMe": "Benim paylaştıklarım", + "doubleYourStorage": "Depolama alanınızı ikiye katlayın", + "referFriendsAnd2xYourPlan": "Arkadaşlarınıza önerin ve planınızı 2 katına çıkarın", + "shareAlbumHint": "Bir albüm açın ve paylaşmak için sağ üstteki paylaş düğmesine dokunun.", + "itemsShowTheNumberOfDaysRemainingBeforePermanentDeletion": "Öğeler, kalıcı olarak silinmeden önce kalan gün sayısını gösterir", + "trashDaysLeft": "{count, plural, =0{gün} =1{1 gün} other{{count} gün}}", + "@trashDaysLeft": { + "description": "Text to indicate number of days remaining before permanent deletion", + "placeholders": { + "count": { + "example": "1|2|3", + "type": "int" + } + } + }, + "deleteAll": "Hepsini Sil", + "renameAlbum": "Albümü yeniden adlandır", + "convertToAlbum": "Albüme taşı", + "setCover": "Kapak Belirle", + "@setCover": { + "description": "Text to set cover photo for an album" + }, + "sortAlbumsBy": "Sırala", + "sortNewestFirst": "Yeniden eskiye", + "sortOldestFirst": "Önce en eski", + "rename": "Yeniden adlandır", + "leaveSharedAlbum": "Paylaşılan albüm silinsin mi?", + "leaveAlbum": "Albümü yeniden adlandır", + "photosAddedByYouWillBeRemovedFromTheAlbum": "Eklediğiniz fotoğraflar albümden kaldırılacak", + "youveNoFilesInThisAlbumThatCanBeDeleted": "Bu cihazda silinebilecek hiçbir dosyanız yok", + "youDontHaveAnyArchivedItems": "Arşivlenmiş öğeniz yok.", + "ignoredFolderUploadReason": "Some files in this album are ignored from upload because they had previously been deleted from Ente.", + "resetIgnoredFiles": "Yok sayılan dosyaları sıfırla", + "deviceFilesAutoUploading": "Files added to this device album will automatically get uploaded to Ente.", + "turnOnBackupForAutoUpload": "Turn on backup to automatically upload files added to this device folder to Ente.", + "noHiddenPhotosOrVideos": "Gizli fotoğraf veya video yok", + "toHideAPhotoOrVideo": "Bir fotoğrafı veya videoyu gizlemek için", + "openTheItem": "• Öğeyi açın", + "clickOnTheOverflowMenu": "• Taşma menüsüne tıklayın", + "click": "• Tıklamak", + "nothingToSeeHere": "Burada görülecek bir şey yok! 👀", + "unarchiveAlbum": "Arşivden Çıkar", + "archiveAlbum": "Albümü arşivle", + "calculating": "Hesaplanıyor...", + "pleaseWaitDeletingAlbum": "Lütfen bekleyin, albüm siliniyor", + "searchByExamples": "• Albüm adları (ör. \"Kamera\")\n• Dosya türleri (ör. \"Videolar\", \".gif\")\n• Yıllar ve aylar (ör. \"2022\", \"Ocak\")\n• Tatiller (ör. \"Noel\")\n• Fotoğraf açıklamaları (ör. \"#eğlence\")", + "youCanTrySearchingForADifferentQuery": "Farklı bir sorgu aramayı deneyebilirsiniz.", + "noResultsFound": "Hiçbir sonuç bulunamadı", + "addedBy": "{emailOrName} tarafından eklendi", + "loadingExifData": "EXIF verileri yükleniyor...", + "viewAllExifData": "Tüm EXIF verilerini görüntüle", + "noExifData": "EXIF verisi yok", + "thisImageHasNoExifData": "Bu görselde exif verisi yok", + "exif": "EXIF", + "noResults": "Sonuç bulunamadı", + "weDontSupportEditingPhotosAndAlbumsThatYouDont": "Henüz sahibi olmadığınız fotoğraf ve albümlerin düzenlenmesini desteklemiyoruz", + "failedToFetchOriginalForEdit": "Düzenleme için orijinal getirilemedi", + "close": "Kapat", + "setAs": "Şu şekilde ayarla", + "fileSavedToGallery": "Video galeriye kaydedildi", + "filesSavedToGallery": "Files saved to gallery", + "fileFailedToSaveToGallery": "Dosya galeriye kaydedilemedi", + "download": "İndir", + "pressAndHoldToPlayVideo": "Videoları yönetmek için basılı tutun", + "pressAndHoldToPlayVideoDetailed": "Videoyu oynatmak için resmi basılı tutun", + "downloadFailed": "İndirme başarısız", + "deduplicateFiles": "Dosyaları Tekilleştirme", + "deselectAll": "Tüm seçimi kaldır", + "reviewDeduplicateItems": "Lütfen kopya olduğunu düşündüğünüz öğeleri inceleyin ve silin.", + "clubByCaptureTime": "Yakalama zamanına göre kulüp", + "clubByFileName": "Dosya adına göre kulüp", + "count": "Miktar", + "totalSize": "Toplam boyut", + "longpressOnAnItemToViewInFullscreen": "Tam ekranda görüntülemek için bir öğeye uzun basın", + "decryptingVideo": "Videonun şifresi çözülüyor...", + "authToViewYourMemories": "Kodlarınızı görmek için lütfen kimlik doğrulaması yapın", + "unlock": "Kilidi aç", + "freeUpSpace": "Boş alan", + "freeUpSpaceSaving": "{count, plural, one {Yer açmak için cihazdan silinebilir {formattedSize}} other {Yer açmak için cihazdan silinebilir {formattedSize}}}", + "filesBackedUpInAlbum": "Bu albümdeki {count, plural, one {1 file} other {{formattedNumber} dosya}} güvenli bir şekilde yedeklendi", + "@filesBackedUpInAlbum": { + "description": "Text to tell user how many files have been backed up in the album", + "placeholders": { + "count": { + "example": "1", + "type": "int" + }, + "formattedNumber": { + "content": "{formattedNumber}", + "example": "1,000", + "type": "String" + } + } + }, + "filesBackedUpFromDevice": "Bu cihazdaki {count, plural, one {1 file} other {{formattedNumber} dosya}} güvenli bir şekilde yedeklendi", + "@filesBackedUpFromDevice": { + "description": "Text to tell user how many files have been backed up from this device", + "placeholders": { + "count": { + "example": "1", + "type": "int" + }, + "formattedNumber": { + "content": "{formattedNumber}", + "example": "1,000", + "type": "String" + } + } + }, + "@freeUpSpaceSaving": { + "description": "Text to tell user how much space they can free up by deleting items from the device" + }, + "freeUpAccessPostDelete": "You can still access {count, plural, one {it} other {them}} on Ente as long as you have an active subscription", + "@freeUpAccessPostDelete": { + "placeholders": { + "count": { + "example": "1", + "type": "int" + } + } + }, + "freeUpAmount": "{sizeInMBorGB} yer açın", + "thisEmailIsAlreadyInUse": "Bu e-posta zaten kullanılıyor", + "incorrectCode": "Yanlış kod", + "authenticationFailedPleaseTryAgain": "Kimlik doğrulama başarısız oldu, lütfen tekrar deneyin", + "verificationFailedPleaseTryAgain": "Doğrulama başarısız oldu, lütfen tekrar deneyin", + "authenticating": "Kimlik doğrulanıyor...", + "authenticationSuccessful": "Kimlik doğrulama başarılı!", + "incorrectRecoveryKey": "Yanlış kurtarma kodu", + "theRecoveryKeyYouEnteredIsIncorrect": "Girdiğiniz kurtarma kodu yanlış", + "twofactorAuthenticationSuccessfullyReset": "İki faktörlü kimlik doğrulama başarıyla sıfırlandı", + "pleaseVerifyTheCodeYouHaveEntered": "Lütfen girdiğiniz kodu doğrulayın", + "pleaseContactSupportIfTheProblemPersists": "Bu hata devam ederse lütfen desteğe başvurun", + "twofactorAuthenticationHasBeenDisabled": "İki faktörlü kimlik doğrulama devre dışı", + "sorryTheCodeYouveEnteredIsIncorrect": "Üzgünüz, girdiğiniz kod yanlış", + "yourVerificationCodeHasExpired": "Doğrulama kodunuzun süresi doldu", + "emailChangedTo": "E-posta {newEmail} olarak değiştirildi", + "verifying": "Doğrulanıyor...", + "disablingTwofactorAuthentication": "İki aşamalı doğrulamayı devre dışı bırak...", + "allMemoriesPreserved": "Tüm anılar saklandı", + "loadingGallery": "Galeri yükleniyor...", + "syncing": "Eşitleniyor...", + "encryptingBackup": "Yedekleme şifreleniyor...", + "syncStopped": "Senkronizasyon durduruldu", + "syncProgress": "{completed}/{total} anı korundu", + "@syncProgress": { + "description": "Text to tell user how many memories have been preserved", + "placeholders": { + "completed": { + "type": "String" + }, + "total": { + "type": "String" + } + } + }, + "archiving": "Arşivleniyor...", + "unarchiving": "Arşivden çıkarılıyor...", + "successfullyArchived": "Başarıyla arşivlendi", + "successfullyUnarchived": "Başarıyla arşivden çıkarıldı", + "renameFile": "Dosyayı yeniden adlandır", + "enterFileName": "Dosya adını girin", + "filesDeleted": "Dosyalar silinmiş", + "selectedFilesAreNotOnEnte": "Selected files are not on Ente", + "thisActionCannotBeUndone": "Bu eylem geri alınamaz", + "emptyTrash": "Çöp kutusu boşaltılsın mı?", + "permDeleteWarning": "Çöp kutusundaki tüm öğeler kalıcı olarak silinecek\n\nBu işlem geri alınamaz", + "empty": "Boşalt", + "couldNotFreeUpSpace": "Yer boşaltılamadı", + "permanentlyDeleteFromDevice": "Cihazdan kalıcı olarak silinsin mi?", + "someOfTheFilesYouAreTryingToDeleteAre": "Silmeye çalıştığınız dosyalardan bazıları yalnızca cihazınızda mevcuttur ve silindiği takdirde kurtarılamaz", + "theyWillBeDeletedFromAllAlbums": "Tüm albümlerden silinecek.", + "someItemsAreInBothEnteAndYourDevice": "Some items are in both Ente and your device.", + "selectedItemsWillBeDeletedFromAllAlbumsAndMoved": "Seçilen öğeler tüm albümlerden silinecek ve çöp kutusuna taşınacak.", + "theseItemsWillBeDeletedFromYourDevice": "Bu öğeler cihazınızdan silinecektir.", + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Bir şeyler ters gitmiş gibi görünüyor. Lütfen bir süre sonra tekrar deneyin. Hata devam ederse, lütfen destek ekibimizle iletişime geçin.", + "error": "Hata", + "tempErrorContactSupportIfPersists": "Bir şeyler ters gitmiş gibi görünüyor. Lütfen bir süre sonra tekrar deneyin. Hata devam ederse, lütfen destek ekibimizle iletişime geçin.", + "networkHostLookUpErr": "Ente'ye bağlanılamıyor. Lütfen ağ ayarlarınızı kontrol edin ve hata devam ederse destek ekibiyle iletişime geçin.", + "networkConnectionRefusedErr": "Ente'ye bağlanılamıyor. Lütfen bir süre sonra tekrar deneyin. Hata devam ederse lütfen desteğe başvurun.", + "cachedData": "Ön belleğe alınan veri", + "clearCaches": "Önbellekleri temizle", + "remoteImages": "Uzaktan Görüntüler", + "remoteVideos": "Uzak videolar", + "remoteThumbnails": "Uzak Küçük Resim", + "pendingSync": "Senkronizasyon bekleniyor", + "localGallery": "Yerel galeri", + "todaysLogs": "Bugünün günlükleri", + "viewLogs": "Günlükleri göster", + "logsDialogBody": "Bu, sorununuzu gidermemize yardımcı olmak için günlükleri gönderecektir. Belirli dosyalarla ilgili sorunların izlenmesine yardımcı olmak için dosya adlarının ekleneceğini lütfen unutmayın.", + "preparingLogs": "Günlük hazırlanıyor...", + "emailYourLogs": "Günlüklerinizi e-postayla gönderin", + "pleaseSendTheLogsTo": "Lütfen günlükleri şu adrese gönderin\n{toEmail}", + "copyEmailAddress": "E-posta adresini kopyala", + "exportLogs": "Günlüğü dışa aktar", + "pleaseEmailUsAt": "Lütfen bize {toEmail} adresinden ulaşın", + "dismiss": "Reddet", + "didYouKnow": "Biliyor musun?", + "loadingMessage": "Fotoğraflarınız yükleniyor...", + "loadMessage1": "Aboneliğinizi ailenizle paylaşabilirsiniz", + "loadMessage2": "Şu ana kadar 30 milyondan fazla anıyı koruduk", + "loadMessage3": "Verilerinizin 3 kopyasını saklıyoruz, biri yer altı serpinti sığınağında", + "loadMessage4": "Tüm uygulamalarımız açık kaynaktır", + "loadMessage5": "Kaynak kodumuz ve şifrelememiz harici olarak denetlenmiştir", + "loadMessage6": "Albümlerinizin bağlantılarını sevdiklerinizle paylaşabilirsiniz", + "loadMessage7": "Mobil uygulamalarımız, tıkladığınız yeni fotoğrafları şifrelemek ve yedeklemek için arka planda çalışır", + "loadMessage8": "web.ente.io'nun mükemmel bir yükleyicisi var", + "loadMessage9": "Verilerinizi güvenli bir şekilde şifrelemek için Xchacha20Poly1305 kullanıyoruz", + "photoDescriptions": "Fotoğraf Açıklaması", + "fileTypesAndNames": "Dosya türleri ve adları", + "location": "Konum", + "moments": "Anlar", + "searchFaceEmptySection": "People will be shown here once indexing is done", + "searchDatesEmptySection": "Tarihe, aya veya yıla göre arama yapın", + "searchLocationEmptySection": "Bir fotoğrafın belli bir yarıçapında çekilen fotoğrafları gruplandırın", + "searchPeopleEmptySection": "İnsanları davet ettiğinizde onların paylaştığı tüm fotoğrafları burada göreceksiniz", + "searchAlbumsEmptySection": "Albümler", + "searchFileTypesAndNamesEmptySection": "Dosya türleri ve adları", + "searchCaptionEmptySection": "Fotoğraf bilgilerini burada hızlı bir şekilde bulmak için \"#trip\" gibi açıklamalar ekleyin", + "language": "Dil", + "selectLanguage": "Dil Seçin", + "locationName": "Konum Adı", + "addLocation": "Konum Ekle", + "groupNearbyPhotos": "Yakındaki fotoğrafları gruplandır", + "kiloMeterUnit": "km", + "addLocationButton": "Ekle", + "radius": "Yarıçap", + "locationTagFeatureDescription": "Bir fotoğrafın belli bir yarıçapında çekilen fotoğrafları gruplandırın", + "galleryMemoryLimitInfo": "Galeride 1000'e kadar anı gösterilir", + "save": "Kaydet", + "centerPoint": "Merkez noktası", + "pickCenterPoint": "Merkez noktasını seçin", + "useSelectedPhoto": "Seçilen fotoğrafı kullan", + "resetToDefault": "Varsayılana sıfırla", + "@resetToDefault": { + "description": "Button text to reset cover photo to default" + }, + "edit": "Düzenle", + "deleteLocation": "Konumu sil", + "rotateLeft": "Sola döndür", + "flip": "Çevir", + "rotateRight": "Sağa döndür", + "saveCopy": "Kopyasını kaydet", + "light": "Aydınlık", + "color": "Renk", + "yesDiscardChanges": "Evet, değişiklikleri sil", + "doYouWantToDiscardTheEditsYouHaveMade": "Yaptığınız düzenlemeleri silmek istiyor musunuz?", + "saving": "Kaydediliyor...", + "editsSaved": "Düzenleme kaydedildi", + "oopsCouldNotSaveEdits": "Hata! Düzenlemeler kaydedilemedi", + "distanceInKMUnit": "km", + "@distanceInKMUnit": { + "description": "Unit for distance in km" + }, + "dayToday": "Bugün", + "dayYesterday": "Dün", + "storage": "Depolama", + "usedSpace": "Kullanılan alan", + "storageBreakupFamily": "Aile", + "storageBreakupYou": "Sen", + "@storageBreakupYou": { + "description": "Label to indicate how much storage you are using when you are part of a family plan" + }, + "storageUsageInfo": "{usedAmount} {usedStorageUnit} / {totalAmount} {totalStorageUnit} kullanıldı", + "@storageUsageInfo": { + "description": "Example: 1.2 GB of 2 GB used or 100 GB or 2TB used" + }, + "availableStorageSpace": "{freeAmount} {storageUnit} free", + "appVersion": "Sürüm: {versionValue}", + "verifyIDLabel": "Doğrula", + "fileInfoAddDescHint": "Bir açıklama ekle...", + "editLocationTagTitle": "Konumu düzenle", + "setLabel": "Ayarla", + "@setLabel": { + "description": "Label of confirm button to add a new custom radius to the radius selector of a location tag" + }, + "setRadius": "Yarıçapı ayarla", + "familyPlanPortalTitle": "Aile", + "familyPlanOverview": "Add 5 family members to your existing plan without paying extra.\n\nEach member gets their own private space, and cannot see each other's files unless they're shared.\n\nFamily plans are available to customers who have a paid Ente subscription.\n\nSubscribe now to get started!", + "androidBiometricHint": "Kimliği doğrula", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Tanınmadı. Tekrar deneyin.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Başarılı", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "İptal et", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Kimlik doğrulaması gerekli", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Biyometrik gerekli", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Cihaz kimlik bilgileri gerekli", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Cihaz kimlik bilgileri gerekmekte", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Ayarlara git", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Biyometrik kimlik doğrulama cihazınızda ayarlanmamış. Biyometrik kimlik doğrulama eklemek için 'Ayarlar > Güvenlik' bölümüne gidin.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Biyometrik kimlik doğrulama devre dışı. Etkinleştirmek için lütfen ekranınızı kilitleyin ve kilidini açın.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSGoToSettingsDescription": "Cihazınızda biyometrik kimlik doğrulama ayarlanmamış. Lütfen telefonunuzda Touch ID veya Face ID'yi etkinleştirin.", + "@iOSGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "Tamam", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "openstreetmapContributors": "© OpenStreetMap katkıda bululanlar", + "hostedAtOsmFrance": "OSM Fransa'da ağırlandı", + "map": "Harita", + "@map": { + "description": "Label for the map view" + }, + "maps": "Haritalar", + "enableMaps": "Haritaları Etkinleştir", + "enableMapsDesc": "Bu, fotoğraflarınızı bir dünya haritasında gösterecektir.\n\nBu harita Open Street Map tarafından barındırılmaktadır ve fotoğraflarınızın tam konumları hiçbir zaman paylaşılmaz.\n\nBu özelliği istediğiniz zaman Ayarlar'dan devre dışı bırakabilirsiniz.", + "quickLinks": "Hızlı Erişim", + "selectItemsToAdd": "Eklenecek eşyaları seçin", + "addSelected": "Seçileni ekle", + "addFromDevice": "Cihazdan ekle", + "addPhotos": "Fotoğraf ekle", + "noPhotosFoundHere": "Burada fotoğraf bulunamadı", + "zoomOutToSeePhotos": "Fotoğrafları görmek için uzaklaştırın", + "noImagesWithLocation": "Konum içeren resim yok", + "unpinAlbum": "Albümün sabitlemesini kaldır", + "pinAlbum": "Albümü sabitle", + "create": "Oluştur", + "viewAll": "Tümünü görüntüle", + "nothingSharedWithYouYet": "Henüz sizinle paylaşılan bir şey yok", + "noAlbumsSharedByYouYet": "Henüz paylaştığınız albüm yok", + "sharedWithYou": "Sizinle paylaşıldı", + "sharedByYou": "Paylaştıklarınız", + "inviteYourFriendsToEnte": "Invite your friends to Ente", + "failedToDownloadVideo": "Video indirilemedi", + "hiding": "Gizleniyor...", + "unhiding": "Gösteriliyor...", + "successfullyHid": "Başarıyla saklandı", + "successfullyUnhid": "Başarıyla arşivden çıkarıldı", + "crashReporting": "Çökme raporlaması", + "addToHiddenAlbum": "Gizli albüme ekle", + "moveToHiddenAlbum": "Gizli albüme ekle", + "fileTypes": "Dosya türü", + "deleteConfirmDialogBody": "This account is linked to other Ente apps, if you use any. Your uploaded data, across all Ente apps, will be scheduled for deletion, and your account will be permanently deleted.", + "hearUsWhereTitle": "Ente'yi nereden duydunuz? (opsiyonel)", + "hearUsExplanation": "Biz uygulama kurulumlarını takip etmiyoruz. Bizi nereden duyduğunuzdan bahsetmeniz bize çok yardımcı olacak!", + "viewAddOnButton": "Eklentileri görüntüle", + "addOns": "Eklentiler", + "addOnPageSubtitle": "Eklentilerin ayrıntıları", + "yourMap": "Haritalarınız", + "modifyYourQueryOrTrySearchingFor": "Sorgunuzu değiştirin veya aramayı deneyin", + "blackFridaySale": "Muhteşem Cuma kampanyası", + "upto50OffUntil4thDec": "4 Aralık'a kadar %50'ye varan indirim.", + "photos": "Fotoğraflar", + "videos": "Videolar", + "livePhotos": "Canlı Fotoğraf", + "searchHint1": "Hızlı, cihaz üzerinde arama", + "searchHint2": "Fotoğraf tarihleri, açıklamalar", + "searchHint3": "Albümler, dosya adları ve türleri", + "searchHint4": "Konum", + "searchHint5": "Çok yakında: Yüzler ve sihirli arama ✨", + "addYourPhotosNow": "Fotoğraflarınızı şimdi ekleyin", + "searchResultCount": "{count, plural, one{{count} yıl önce} other{{count} yıl önce}}", + "@searchResultCount": { + "description": "Text to tell user how many results were found for their search query", + "placeholders": { + "count": { + "example": "1|2|3", + "type": "int" + } + } + }, + "faces": "Yüzler", + "people": "People", + "contents": "İçerikler", + "addNew": "Yeni ekle", + "@addNew": { + "description": "Text to add a new item (location tag, album, caption etc)" + }, + "contacts": "Kişiler", + "noInternetConnection": "İnternet bağlantısı yok", + "pleaseCheckYourInternetConnectionAndTryAgain": "Lütfen internet bağlantınızı kontrol edin ve yeniden deneyin.", + "signOutFromOtherDevices": "Diğer cihazlardan çıkış yap", + "signOutOtherBody": "Eğer başka birisinin parolanızı bildiğini düşünüyorsanız, diğer tüm cihazları hesabınızdan çıkışa zorlayabilirsiniz.", + "signOutOtherDevices": "Diğer cihazlardan çıkış yap", + "doNotSignOut": "Çıkış yapma", + "editLocation": "Konumu düzenle", + "selectALocation": "Bir konum seçin", + "selectALocationFirst": "Önce yeni yer seçin", + "changeLocationOfSelectedItems": "Seçilen öğelerin konumu değiştirilsin mi?", + "editsToLocationWillOnlyBeSeenWithinEnte": "Konumda yapılan düzenlemeler yalnızca Ente'de görülecektir", + "cleanUncategorized": "Temiz Genel", + "cleanUncategorizedDescription": "Diğer albümlerde bulunan Kategorilenmemiş tüm dosyaları kaldırın", + "waitingForVerification": "Doğrulama bekleniyor...", + "passkey": "Parola Anahtarı", + "passkeyAuthTitle": "Geçiş anahtarı doğrulaması", + "passKeyPendingVerification": "Verification is still pending", + "loginSessionExpired": "Session expired", + "loginSessionExpiredDetails": "Your session has expired. Please login again.", + "verifyPasskey": "Şifrenizi doğrulayın", + "playOnTv": "Albümü TV'de oynat", + "pair": "Eşleştir", + "deviceNotFound": "Cihaz bulunamadı", + "castInstruction": "Eşleştirmek istediğiniz cihazda cast.ente.io adresini ziyaret edin.\n\nAlbümü TV'nizde oynatmak için aşağıdaki kodu girin.", + "deviceCodeHint": "Kodu girin", + "joinDiscord": "Discord'a Katıl", + "locations": "Konum", + "descriptions": "Açıklama", + "addAName": "Add a name", + "findPeopleByName": "Find people quickly by name", + "addViewers": "{count, plural, zero {Görüntüleyen ekle} one {Görüntüleyen ekle} other {Görüntüleyen ekle}}", + "addCollaborators": "{count, plural, zero {Ortak çalışan ekle} one {Ortak çalışan ekle} other {Ortak çalışan ekle}}", + "longPressAnEmailToVerifyEndToEndEncryption": "Uçtan uca şifrelemeyi doğrulamak için bir e-postaya uzun basın.", + "developerSettingsWarning": "Geliştirici ayarlarını değiştirmek istediğinizden emin misiniz?", + "developerSettings": "Geliştirici ayarları", + "serverEndpoint": "Sunucu uç noktası", + "invalidEndpoint": "Geçersiz uç nokta", + "invalidEndpointMessage": "Üzgünüz, girdiğiniz uç nokta geçersiz. Lütfen geçerli bir uç nokta girin ve tekrar deneyin.", + "endpointUpdatedMessage": "Fatura başarıyla güncellendi", + "customEndpoint": "{endpoint}'e bağlanıldı", + "createCollaborativeLink": "Create collaborative link", + "search": "Search", + "enterPersonName": "Enter person name", + "removePersonLabel": "Remove person label", + "autoPairDesc": "Auto pair works only with devices that support Chromecast.", + "manualPairDesc": "Pair with PIN works with any screen you wish to view your album on.", + "connectToDevice": "Connect to device", + "autoCastDialogBody": "You'll see available Cast devices here.", + "autoCastiOSPermission": "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings.", + "noDeviceFound": "No device found", + "stopCastingTitle": "Stop casting", + "stopCastingBody": "Do you want to stop casting?", + "castIPMismatchTitle": "Failed to cast album", + "castIPMismatchBody": "Please make sure you are on the same network as the TV.", + "pairingComplete": "Pairing complete", + "savingEdits": "Saving edits...", + "autoPair": "Auto pair", + "pairWithPin": "Pair with PIN", + "faceRecognition": "Face recognition", + "foundFaces": "Found faces", + "clusteringProgress": "Clustering progress", + "indexingIsPaused": "Indexing is paused. It will automatically resume when device is ready.", + "trim": "Trim", + "crop": "Crop", + "rotate": "Rotate", + "left": "Left", + "right": "Right", + "whatsNew": "What's new", + "reviewSuggestions": "Review suggestions", + "useAsCover": "Use as cover", + "notPersonLabel": "Not {name}?", + "@notPersonLabel": { + "description": "Label to indicate that the person in the photo is not the person whose name is mentioned", + "placeholders": { + "name": { + "content": "{name}", + "type": "String" + } + } + }, + "reenterPassword": "Re-enter password", + "reenterPin": "Re-enter PIN", + "deviceLock": "Device lock", + "pinLock": "PIN lock", + "next": "Next", + "setNewPassword": "Set new password", + "enterPin": "Enter PIN", + "setNewPin": "Set new PIN", + "appLock": "App lock", + "noSystemLockFound": "No system lock found", + "toEnableAppLockPleaseSetupDevicePasscodeOrScreen": "To enable app lock, please setup device passcode or screen lock in your system settings.", + "tapToUnlock": "Tap to unlock", + "tooManyIncorrectAttempts": "Too many incorrect attempts" +} \ No newline at end of file diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 15a0ac68b3..01969b46d7 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "61.0.0" + version: "67.0.0" _flutterfire_internals: dependency: transitive description: @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "6.4.1" animate_do: dependency: "direct main" description: @@ -1236,6 +1236,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.18.1" + intl_utils: + dependency: "direct dev" + description: + name: intl_utils + sha256: c2b1f5c72c25512cbeef5ab015c008fc50fe7e04813ba5541c25272300484bf4 + url: "https://pub.dev" + source: hosted + version: "2.8.7" io: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 8b2cbcfe43..b187f3c09a 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -204,6 +204,7 @@ dev_dependencies: freezed: ^2.5.2 integration_test: sdk: flutter + intl_utils: ^2.8.7 json_serializable: ^6.6.1 test: ^1.22.0 diff --git a/server/Dockerfile b/server/Dockerfile index d902deebfd..778d5ed8ee 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21-alpine3.17 as builder +FROM golang:1.21-alpine3.17 AS builder RUN apk add --no-cache gcc musl-dev git build-base pkgconfig libsodium-dev ENV GOOS=linux diff --git a/server/pkg/repo/passkey/passkey.go b/server/pkg/repo/passkey/passkey.go index 2c46320212..603c3ab26c 100644 --- a/server/pkg/repo/passkey/passkey.go +++ b/server/pkg/repo/passkey/passkey.go @@ -47,7 +47,9 @@ func (u *PasskeyUser) WebAuthnName() string { } func (u *PasskeyUser) WebAuthnDisplayName() string { - return u.Name + // Safari requires a display name to be set, otherwise it does not recognize + // security keys. + return u.Email } func (u *PasskeyUser) WebAuthnCredentials() []webauthn.Credential { diff --git a/web/apps/accounts/src/pages/_app.tsx b/web/apps/accounts/src/pages/_app.tsx index 7029cacd86..2cf427b918 100644 --- a/web/apps/accounts/src/pages/_app.tsx +++ b/web/apps/accounts/src/pages/_app.tsx @@ -33,8 +33,10 @@ const App: React.FC = ({ Component, pageProps }) => { useEffect(() => { disableDiskLogs(); // The accounts app has no local state, but some older builds might've - // leftover some scraps. Clear it out. This code added 1 July 2024, can - // be removed after a while (tag: Migration). + // leftover some scraps. Clear it out. + // + // This code added on 1 July 2024, can be removed soon since this data + // was never saved before this was released (tag: Migration). clearData(); void setupI18n().finally(() => setIsI18nReady(true)); logUnhandledErrorsAndRejections(true); diff --git a/web/apps/accounts/src/pages/passkeys/index.tsx b/web/apps/accounts/src/pages/passkeys/index.tsx index 6f201ff33a..1ca210d5f6 100644 --- a/web/apps/accounts/src/pages/passkeys/index.tsx +++ b/web/apps/accounts/src/pages/passkeys/index.tsx @@ -1,16 +1,15 @@ +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { MenuItemDivider, MenuItemGroup } from "@/new/shared/components/Menu"; +import { Titlebar } from "@/new/shared/components/Titlebar"; import log from "@/next/log"; import { ensure } from "@/utils/ensure"; import { CenteredFlex } from "@ente/shared/components/Container"; import DialogBoxV2 from "@ente/shared/components/DialogBoxV2"; import EnteButton from "@ente/shared/components/EnteButton"; -import { EnteDrawer } from "@ente/shared/components/EnteDrawer"; import FormPaper from "@ente/shared/components/Form/FormPaper"; import InfoItem from "@ente/shared/components/Info/InfoItem"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; -import MenuItemDivider from "@ente/shared/components/Menu/MenuItemDivider"; -import { MenuItemGroup } from "@ente/shared/components/Menu/MenuItemGroup"; import SingleInputForm from "@ente/shared/components/SingleInputForm"; -import Titlebar from "@ente/shared/components/Titlebar"; import { formatDateTimeFull } from "@ente/shared/time/format"; import CalendarTodayIcon from "@mui/icons-material/CalendarToday"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; diff --git a/web/apps/accounts/src/services/passkey.ts b/web/apps/accounts/src/services/passkey.ts index 469881e5c6..8f2f15b6d2 100644 --- a/web/apps/accounts/src/services/passkey.ts +++ b/web/apps/accounts/src/services/passkey.ts @@ -454,7 +454,61 @@ export const beginPasskeyAuthentication = async ( */ export const signChallenge = async ( publicKey: PublicKeyCredentialRequestOptions, -) => nullToUndefined(await navigator.credentials.get({ publicKey })); +) => { + // Hint all transports to make security keys like Yubikey work across + // varying registration/verification scenarios. + // + // During verification, we need to pass a `transport` property. + // + // > The `transports` property is hint of the methods that the client could + // > use to communicate with the relevant authenticator of the public key + // > credential to retrieve. Possible values are ["ble", "hybrid", + // > "internal", "nfc", "usb"]. + // > + // > MDN + // + // When we register a passkey, we save the transport alongwith the + // credential. During authentication, we pass that transport back to the + // browser. This is the approach recommended by the spec: + // + // > When registering a new credential, the Relying Party SHOULD store the + // > value returned from getTransports(). When creating a + // > PublicKeyCredentialDescriptor for that credential, the Relying Party + // > SHOULD retrieve that stored value and set it as the value of the + // > transports member. + // > + // > https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptor-transports + // + // However, following this recommendation break things currently (2024) in + // various ways. For example, if a user registers a Yubikey NFC security key + // on Firefox on their laptop, then Firefox returns ["usb"]. This is + // incorrect, it should be ["usb", "nfc"] (which is what Chrome does, since + // the hardware itself supports both USB and NFC transports). + // + // Later, if the user tries to verifying with their security key on their + // iPhone Safari via NFC, the browser doesn't recognize it (which seems + // incorrect too, the transport is meant to be a "hint" not a binding). + // + // > Note that these hints represent the WebAuthn Relying Party's best + // > belief as to how an authenticator may be reached. + // > + // > https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptor-transports + // + // As a workaround, we override transports with known possible values. + + for (const cred of publicKey.allowCredentials ?? []) { + cred.transports = [ + ...(cred.transports ?? []), + "usb", + "nfc", + "ble", + "hybrid", + "internal", + ]; + } + + return nullToUndefined(await navigator.credentials.get({ publicKey })); +}; interface FinishPasskeyAuthenticationOptions { passkeySessionID: string; diff --git a/web/apps/photos/src/components/Collections/CollectionShare/emailShare/AddParticipant.tsx b/web/apps/photos/src/components/Collections/CollectionShare/emailShare/AddParticipant.tsx index 05437ada00..5d833642df 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare/emailShare/AddParticipant.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare/emailShare/AddParticipant.tsx @@ -1,6 +1,6 @@ +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { Titlebar } from "@/new/shared/components/Titlebar"; import { DialogProps, Stack } from "@mui/material"; -import { EnteDrawer } from "components/EnteDrawer"; -import Titlebar from "components/Titlebar"; import { t } from "i18next"; import { COLLECTION_ROLE, Collection } from "types/collection"; diff --git a/web/apps/photos/src/components/Collections/CollectionShare/emailShare/AddParticipantForm.tsx b/web/apps/photos/src/components/Collections/CollectionShare/emailShare/AddParticipantForm.tsx index 29e8be1015..9bf36991d4 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare/emailShare/AddParticipantForm.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare/emailShare/AddParticipantForm.tsx @@ -1,12 +1,14 @@ +import { + MenuItemDivider, + MenuItemGroup, + MenuSectionTitle, +} from "@/new/shared/components/Menu"; import { FlexWrapper } from "@ente/shared/components/Container"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import SubmitButton from "@ente/shared/components/SubmitButton"; import DoneIcon from "@mui/icons-material/Done"; import { Button, FormHelperText, Stack } from "@mui/material"; import TextField from "@mui/material/TextField"; -import MenuItemDivider from "components/Menu/MenuItemDivider"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import MenuSectionTitle from "components/Menu/MenuSectionTitle"; import Avatar from "components/pages/gallery/Avatar"; import { Formik, type FormikHelpers } from "formik"; import { t } from "i18next"; diff --git a/web/apps/photos/src/components/Collections/CollectionShare/emailShare/ManageEmailShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare/emailShare/ManageEmailShare.tsx index 78e5dfe7ef..972b11019e 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare/emailShare/ManageEmailShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare/emailShare/ManageEmailShare.tsx @@ -1,3 +1,10 @@ +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { + MenuItemDivider, + MenuItemGroup, + MenuSectionTitle, +} from "@/new/shared/components/Menu"; +import { Titlebar } from "@/new/shared/components/Titlebar"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import Add from "@mui/icons-material/Add"; import AdminPanelSettingsIcon from "@mui/icons-material/AdminPanelSettings"; @@ -5,11 +12,6 @@ import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import ModeEditIcon from "@mui/icons-material/ModeEdit"; import Photo from "@mui/icons-material/Photo"; import { DialogProps, Stack } from "@mui/material"; -import { EnteDrawer } from "components/EnteDrawer"; -import MenuItemDivider from "components/Menu/MenuItemDivider"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import MenuSectionTitle from "components/Menu/MenuSectionTitle"; -import Titlebar from "components/Titlebar"; import Avatar from "components/pages/gallery/Avatar"; import { t } from "i18next"; import { AppContext } from "pages/_app"; diff --git a/web/apps/photos/src/components/Collections/CollectionShare/emailShare/ManageParticipant.tsx b/web/apps/photos/src/components/Collections/CollectionShare/emailShare/ManageParticipant.tsx index c444c63faf..fd8421ed5d 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare/emailShare/ManageParticipant.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare/emailShare/ManageParticipant.tsx @@ -1,3 +1,6 @@ +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { MenuItemDivider, MenuItemGroup } from "@/new/shared/components/Menu"; +import { Titlebar } from "@/new/shared/components/Titlebar"; import log from "@/next/log"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import BlockIcon from "@mui/icons-material/Block"; @@ -5,10 +8,6 @@ import DoneIcon from "@mui/icons-material/Done"; import ModeEditIcon from "@mui/icons-material/ModeEdit"; import PhotoIcon from "@mui/icons-material/Photo"; import { DialogProps, Stack, Typography } from "@mui/material"; -import { EnteDrawer } from "components/EnteDrawer"; -import MenuItemDivider from "components/Menu/MenuItemDivider"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import Titlebar from "components/Titlebar"; import { t } from "i18next"; import { AppContext } from "pages/_app"; import { GalleryContext } from "pages/gallery"; diff --git a/web/apps/photos/src/components/Collections/CollectionShare/emailShare/index.tsx b/web/apps/photos/src/components/Collections/CollectionShare/emailShare/index.tsx index 9d2444e8aa..768e91d5e1 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare/emailShare/index.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare/emailShare/index.tsx @@ -1,14 +1,16 @@ import { useRef, useState } from "react"; import { COLLECTION_ROLE, Collection } from "types/collection"; +import { + MenuItemDivider, + MenuItemGroup, + MenuSectionTitle, +} from "@/new/shared/components/Menu"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import AddIcon from "@mui/icons-material/Add"; import ChevronRight from "@mui/icons-material/ChevronRight"; import Workspaces from "@mui/icons-material/Workspaces"; import { Stack } from "@mui/material"; -import MenuItemDivider from "components/Menu/MenuItemDivider"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import MenuSectionTitle from "components/Menu/MenuSectionTitle"; import AvatarGroup from "components/pages/gallery/AvatarGroup"; import { t } from "i18next"; import AddParticipant from "./AddParticipant"; diff --git a/web/apps/photos/src/components/Collections/CollectionShare/index.tsx b/web/apps/photos/src/components/Collections/CollectionShare/index.tsx index 22de9b55e8..0e675fcafa 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare/index.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare/index.tsx @@ -1,6 +1,6 @@ +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { Titlebar } from "@/new/shared/components/Titlebar"; import { DialogProps, Stack } from "@mui/material"; -import { EnteDrawer } from "components/EnteDrawer"; -import Titlebar from "components/Titlebar"; import { CollectionSummaryType } from "constants/collection"; import { t } from "i18next"; import { Collection, CollectionSummary } from "types/collection"; diff --git a/web/apps/photos/src/components/Collections/CollectionShare/publicShare/EnablePublicShareOptions.tsx b/web/apps/photos/src/components/Collections/CollectionShare/publicShare/EnablePublicShareOptions.tsx index 5df152e0c7..e6a9baf3cc 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare/publicShare/EnablePublicShareOptions.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare/publicShare/EnablePublicShareOptions.tsx @@ -1,11 +1,13 @@ +import { + MenuItemDivider, + MenuItemGroup, + MenuSectionTitle, +} from "@/new/shared/components/Menu"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import DownloadSharp from "@mui/icons-material/DownloadSharp"; import LinkIcon from "@mui/icons-material/Link"; import PublicIcon from "@mui/icons-material/Public"; import { Stack, Typography } from "@mui/material"; -import MenuItemDivider from "components/Menu/MenuItemDivider"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import MenuSectionTitle from "components/Menu/MenuSectionTitle"; import { t } from "i18next"; import { GalleryContext } from "pages/gallery"; import { useContext, useState } from "react"; diff --git a/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/deviceLimit.tsx b/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/deviceLimit.tsx index aacd0f2185..4090b08ec1 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/deviceLimit.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/deviceLimit.tsx @@ -1,10 +1,9 @@ +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { MenuItemDivider, MenuItemGroup } from "@/new/shared/components/Menu"; +import { Titlebar } from "@/new/shared/components/Titlebar"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import ChevronRight from "@mui/icons-material/ChevronRight"; import { DialogProps, Stack } from "@mui/material"; -import { EnteDrawer } from "components/EnteDrawer"; -import MenuItemDivider from "components/Menu/MenuItemDivider"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import Titlebar from "components/Titlebar"; import { t } from "i18next"; import { useMemo, useState } from "react"; import { Collection, PublicURL, UpdatePublicURL } from "types/collection"; diff --git a/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/index.tsx b/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/index.tsx index be93a36a10..5cb996e86f 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/index.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/index.tsx @@ -1,11 +1,10 @@ +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { MenuItemDivider, MenuItemGroup } from "@/new/shared/components/Menu"; +import { Titlebar } from "@/new/shared/components/Titlebar"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import ContentCopyIcon from "@mui/icons-material/ContentCopy"; import RemoveCircleOutline from "@mui/icons-material/RemoveCircleOutline"; import { DialogProps, Stack, Typography } from "@mui/material"; -import { EnteDrawer } from "components/EnteDrawer"; -import MenuItemDivider from "components/Menu/MenuItemDivider"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import Titlebar from "components/Titlebar"; import { t } from "i18next"; import { GalleryContext } from "pages/gallery"; import { useContext, useState } from "react"; diff --git a/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/linkExpiry.tsx b/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/linkExpiry.tsx index 458fcd20a1..6cafd68c3b 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/linkExpiry.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/linkExpiry.tsx @@ -1,11 +1,10 @@ +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { MenuItemDivider, MenuItemGroup } from "@/new/shared/components/Menu"; +import { Titlebar } from "@/new/shared/components/Titlebar"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import { formatDateTime } from "@ente/shared/time/format"; import ChevronRight from "@mui/icons-material/ChevronRight"; import { DialogProps, Stack } from "@mui/material"; -import { EnteDrawer } from "components/EnteDrawer"; -import MenuItemDivider from "components/Menu/MenuItemDivider"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import Titlebar from "components/Titlebar"; import { t } from "i18next"; import { useMemo, useState } from "react"; import { Collection, PublicURL, UpdatePublicURL } from "types/collection"; diff --git a/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/publicCollect.tsx b/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/publicCollect.tsx index bfd78f1a31..4290cd349f 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/publicCollect.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare/publicShare/manage/publicCollect.tsx @@ -1,7 +1,6 @@ +import { MenuItemGroup, MenuSectionTitle } from "@/new/shared/components/Menu"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import { Stack } from "@mui/material"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import MenuSectionTitle from "components/Menu/MenuSectionTitle"; import { t } from "i18next"; import { Collection, PublicURL, UpdatePublicURL } from "types/collection"; diff --git a/web/apps/photos/src/components/Collections/CollectionShare/publicShare/managePublicShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare/publicShare/managePublicShare.tsx index 677228d55f..1f723ae161 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare/publicShare/managePublicShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare/publicShare/managePublicShare.tsx @@ -1,3 +1,4 @@ +import { MenuItemDivider, MenuItemGroup } from "@/new/shared/components/Menu"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import ContentCopyIcon from "@mui/icons-material/ContentCopyOutlined"; @@ -5,8 +6,6 @@ import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; import LinkIcon from "@mui/icons-material/Link"; import PublicIcon from "@mui/icons-material/Public"; import { Stack, Typography } from "@mui/material"; -import MenuItemDivider from "components/Menu/MenuItemDivider"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; import { t } from "i18next"; import { useState } from "react"; import { Collection, PublicURL } from "types/collection"; diff --git a/web/apps/photos/src/components/Collections/CollectionShare/sharingDetails.tsx b/web/apps/photos/src/components/Collections/CollectionShare/sharingDetails.tsx index e7ae9011cc..25e3771fc1 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare/sharingDetails.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare/sharingDetails.tsx @@ -1,11 +1,13 @@ +import { + MenuItemDivider, + MenuItemGroup, + MenuSectionTitle, +} from "@/new/shared/components/Menu"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import AdminPanelSettingsIcon from "@mui/icons-material/AdminPanelSettings"; import ModeEditIcon from "@mui/icons-material/ModeEdit"; import Photo from "@mui/icons-material/Photo"; import { Stack } from "@mui/material"; -import MenuItemDivider from "components/Menu/MenuItemDivider"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import MenuSectionTitle from "components/Menu/MenuSectionTitle"; import Avatar from "components/pages/gallery/Avatar"; import { CollectionSummaryType } from "constants/collection"; import { t } from "i18next"; diff --git a/web/apps/photos/src/components/Menu/MenuItemDivider.tsx b/web/apps/photos/src/components/Menu/MenuItemDivider.tsx deleted file mode 100644 index da3b309a2b..0000000000 --- a/web/apps/photos/src/components/Menu/MenuItemDivider.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Divider } from "@mui/material"; -interface Iprops { - hasIcon?: boolean; -} -export default function MenuItemDivider({ hasIcon = false }: Iprops) { - return ( - - ); -} diff --git a/web/apps/photos/src/components/Menu/MenuItemGroup.tsx b/web/apps/photos/src/components/Menu/MenuItemGroup.tsx deleted file mode 100644 index 0b80262b5f..0000000000 --- a/web/apps/photos/src/components/Menu/MenuItemGroup.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { styled } from "@mui/material"; - -export const MenuItemGroup = styled("div")( - ({ theme }) => ` - & > .MuiMenuItem-root{ - border-radius: 8px; - background-color: transparent; - } - & > .MuiMenuItem-root:not(:last-of-type) { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - & > .MuiMenuItem-root:not(:first-of-type) { - border-top-left-radius: 0; - border-top-right-radius: 0; - } - background-color: ${theme.colors.fill.faint}; - border-radius: 8px; -`, -); diff --git a/web/apps/photos/src/components/Menu/MenuSectionTitle.tsx b/web/apps/photos/src/components/Menu/MenuSectionTitle.tsx deleted file mode 100644 index 5c07b8d92b..0000000000 --- a/web/apps/photos/src/components/Menu/MenuSectionTitle.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { VerticallyCenteredFlex } from "@ente/shared/components/Container"; -import { Typography } from "@mui/material"; - -interface Iprops { - title: string; - icon?: JSX.Element; -} - -export default function MenuSectionTitle({ title, icon }: Iprops) { - return ( - svg": { - fontSize: "17px", - color: (theme) => theme.colors.stroke.muted, - }, - }} - > - {icon && icon} - - {title} - - - ); -} diff --git a/web/apps/photos/src/components/PhotoViewer/FileInfo/ExifData.tsx b/web/apps/photos/src/components/PhotoViewer/FileInfo/ExifData.tsx index c6c0582f8e..d84aa106c3 100644 --- a/web/apps/photos/src/components/PhotoViewer/FileInfo/ExifData.tsx +++ b/web/apps/photos/src/components/PhotoViewer/FileInfo/ExifData.tsx @@ -1,7 +1,7 @@ +import { Titlebar } from "@/new/shared/components/Titlebar"; import CopyButton from "@ente/shared/components/CodeBlock/CopyButton"; import { formatDateTimeFull } from "@ente/shared/time/format"; import { Box, Stack, styled, Typography } from "@mui/material"; -import Titlebar from "components/Titlebar"; import { t } from "i18next"; import React from "react"; import { FileInfoSidebar } from "."; diff --git a/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx b/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx index 158277ff23..d486efb990 100644 --- a/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx @@ -1,5 +1,8 @@ +import { UnidentifiedFaces } from "@/new/photos/components/PeopleList"; import { isMLEnabled } from "@/new/photos/services/ml"; import { EnteFile } from "@/new/photos/types/file"; +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { Titlebar } from "@/new/shared/components/Titlebar"; import CopyButton from "@ente/shared/components/CodeBlock/CopyButton"; import { FlexWrapper } from "@ente/shared/components/Container"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; @@ -11,9 +14,6 @@ import LocationOnOutlined from "@mui/icons-material/LocationOnOutlined"; import TextSnippetOutlined from "@mui/icons-material/TextSnippetOutlined"; import { Box, DialogProps, Link, Stack, styled } from "@mui/material"; import { Chip } from "components/Chip"; -import { EnteDrawer } from "components/EnteDrawer"; -import Titlebar from "components/Titlebar"; -import { UnidentifiedFaces } from "components/ml/PeopleList"; import LinkButton from "components/pages/gallery/LinkButton"; import { t } from "i18next"; import { AppContext } from "pages/_app"; diff --git a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/ColoursMenu.tsx b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/ColoursMenu.tsx index deaebe2cf2..7f7fcf5112 100644 --- a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/ColoursMenu.tsx +++ b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/ColoursMenu.tsx @@ -1,7 +1,6 @@ +import { MenuItemGroup, MenuSectionTitle } from "@/new/shared/components/Menu"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import { Box, Slider } from "@mui/material"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import MenuSectionTitle from "components/Menu/MenuSectionTitle"; import { t } from "i18next"; import type { Dispatch, SetStateAction } from "react"; diff --git a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/CropMenu.tsx b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/CropMenu.tsx index 11916a13a1..923998b009 100644 --- a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/CropMenu.tsx +++ b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/CropMenu.tsx @@ -1,7 +1,6 @@ +import { MenuItemGroup, MenuSectionTitle } from "@/new/shared/components/Menu"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import CropIcon from "@mui/icons-material/Crop"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import MenuSectionTitle from "components/Menu/MenuSectionTitle"; import { t } from "i18next"; import type { MutableRefObject } from "react"; import { useContext } from "react"; diff --git a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/TransformMenu.tsx b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/TransformMenu.tsx index 6176ab3c18..9c578c0261 100644 --- a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/TransformMenu.tsx +++ b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/TransformMenu.tsx @@ -1,3 +1,8 @@ +import { + MenuItemDivider, + MenuItemGroup, + MenuSectionTitle, +} from "@/new/shared/components/Menu"; import log from "@/next/log"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import Crop169Icon from "@mui/icons-material/Crop169"; @@ -6,9 +11,6 @@ import CropSquareIcon from "@mui/icons-material/CropSquare"; import FlipIcon from "@mui/icons-material/Flip"; import RotateLeftIcon from "@mui/icons-material/RotateLeft"; import RotateRightIcon from "@mui/icons-material/RotateRight"; -import MenuItemDivider from "components/Menu/MenuItemDivider"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import MenuSectionTitle from "components/Menu/MenuSectionTitle"; import { t } from "i18next"; import { Fragment, useContext } from "react"; import { ImageEditorOverlayContext } from "."; diff --git a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx index fe7f2724e2..94002c664f 100644 --- a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx @@ -1,5 +1,11 @@ import downloadManager from "@/new/photos/services/download"; import { EnteFile } from "@/new/photos/types/file"; +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { + MenuItemDivider, + MenuItemGroup, + MenuSectionTitle, +} from "@/new/shared/components/Menu"; import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; import { ensure } from "@/utils/ensure"; @@ -26,10 +32,6 @@ import { Tabs, Typography, } from "@mui/material"; -import { EnteDrawer } from "components/EnteDrawer"; -import MenuItemDivider from "components/Menu/MenuItemDivider"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import MenuSectionTitle from "components/Menu/MenuSectionTitle"; import { CORNER_THRESHOLD, FILTER_DEFAULT_VALUES } from "constants/photoEditor"; import { t } from "i18next"; import { AppContext } from "pages/_app"; diff --git a/web/apps/photos/src/components/Search/SearchBar/searchInput/MenuWithPeople.tsx b/web/apps/photos/src/components/Search/SearchBar/searchInput/MenuWithPeople.tsx index 6242090dfe..56dbd305fb 100644 --- a/web/apps/photos/src/components/Search/SearchBar/searchInput/MenuWithPeople.tsx +++ b/web/apps/photos/src/components/Search/SearchBar/searchInput/MenuWithPeople.tsx @@ -1,7 +1,7 @@ +import { PeopleList } from "@/new/photos/components/PeopleList"; import { isMLEnabled } from "@/new/photos/services/ml"; import { Row } from "@ente/shared/components/Container"; import { Box, styled } from "@mui/material"; -import { PeopleList } from "components/ml/PeopleList"; import { t } from "i18next"; import { components } from "react-select"; import { Suggestion, SuggestionType } from "types/search"; diff --git a/web/apps/photos/src/components/Sidebar/AdvancedSettings.tsx b/web/apps/photos/src/components/Sidebar/AdvancedSettings.tsx index 9ef7a26973..5dfd95b30c 100644 --- a/web/apps/photos/src/components/Sidebar/AdvancedSettings.tsx +++ b/web/apps/photos/src/components/Sidebar/AdvancedSettings.tsx @@ -1,24 +1,27 @@ +import { MLSettingsBeta } from "@/new/photos/components/MLSettingsBeta"; +import { canEnableML } from "@/new/photos/services/ml"; +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { MenuItemGroup, MenuSectionTitle } from "@/new/shared/components/Menu"; +import { Titlebar } from "@/new/shared/components/Titlebar"; +import { isDesktop } from "@/next/app"; +import { pt } from "@/next/i18n"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import ChevronRight from "@mui/icons-material/ChevronRight"; import ScienceIcon from "@mui/icons-material/Science"; import { Box, DialogProps, Stack } from "@mui/material"; -import { EnteDrawer } from "components/EnteDrawer"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import MenuSectionTitle from "components/Menu/MenuSectionTitle"; -import Titlebar from "components/Titlebar"; -import { MLSearchSettings } from "components/ml/MLSearchSettings"; import { t } from "i18next"; -import isElectron from "is-electron"; import { AppContext } from "pages/_app"; -import { useContext, useState } from "react"; +import { useContext, useEffect, useState } from "react"; export default function AdvancedSettings({ open, onClose, onRootClose }) { const appContext = useContext(AppContext); - const [mlSearchSettingsView, setMlSearchSettingsView] = useState(false); - const openMlSearchSettings = () => setMlSearchSettingsView(true); - const closeMlSearchSettings = () => setMlSearchSettingsView(false); + const [showMLSettings, setShowMLSettings] = useState(false); + const [openMLSettings, setOpenMLSettings] = useState(false); + useEffect(() => { + if (isDesktop) void canEnableML().then(setShowMLSettings); + }, []); const handleRootClose = () => { onClose(); onRootClose(); @@ -36,18 +39,6 @@ export default function AdvancedSettings({ open, onClose, onRootClose }) { appContext.setIsCFProxyDisabled(!appContext.isCFProxyDisabled); }; - // TODO-ML: - // const [indexingStatus, setIndexingStatus] = useState({ - // indexed: 0, - // pending: 0, - // }); - - // useEffect(() => { - // clipService.setOnUpdateHandler(setIndexingStatus); - // clipService.getIndexingStatus().then((st) => setIndexingStatus(st)); - // return () => clipService.setOnUpdateHandler(undefined); - // }, []); - return ( - {isElectron() && ( - - } - /> - - } - onClick={openMlSearchSettings} - label={t("ML_SEARCH")} - /> - - - )} + - {/* TODO-ML: isElectron() && ( - - + } + /> + + } + onClick={() => setOpenMLSettings(true)} + label={pt("ML search")} /> - - - - {t("INDEXED_ITEMS")} - - - {formatNumber( - indexingStatus.indexed, - )} - - - - - {t("PENDING_ITEMS")} - - - {formatNumber( - indexingStatus.pending, - )} - - - - - )*/} - + + + )} - setOpenMLSettings(false)} onRootClose={handleRootClose} /> diff --git a/web/apps/photos/src/components/Sidebar/MapSetting.tsx b/web/apps/photos/src/components/Sidebar/MapSetting.tsx index 18d1a0639a..60d71dca66 100644 --- a/web/apps/photos/src/components/Sidebar/MapSetting.tsx +++ b/web/apps/photos/src/components/Sidebar/MapSetting.tsx @@ -1,3 +1,6 @@ +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { MenuItemGroup } from "@/new/shared/components/Menu"; +import { Titlebar } from "@/new/shared/components/Titlebar"; import log from "@/next/log"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import { @@ -8,9 +11,6 @@ import { Stack, Typography, } from "@mui/material"; -import { EnteDrawer } from "components/EnteDrawer"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import Titlebar from "components/Titlebar"; import { t } from "i18next"; import { AppContext } from "pages/_app"; import { useContext, useEffect, useState } from "react"; diff --git a/web/apps/photos/src/components/Sidebar/Preferences.tsx b/web/apps/photos/src/components/Sidebar/Preferences.tsx index 0fe44712e2..32c36f991c 100644 --- a/web/apps/photos/src/components/Sidebar/Preferences.tsx +++ b/web/apps/photos/src/components/Sidebar/Preferences.tsx @@ -1,23 +1,32 @@ +import { MLSettings } from "@/new/photos/components/MLSettings"; +import { isMLSupported } from "@/new/photos/services/ml"; +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { MenuItemGroup, MenuSectionTitle } from "@/new/shared/components/Menu"; +import { Titlebar } from "@/new/shared/components/Titlebar"; import { getLocaleInUse, + pt, setLocaleInUse, supportedLocales, type SupportedLocale, } from "@/next/i18n"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import ChevronRight from "@mui/icons-material/ChevronRight"; +import ScienceIcon from "@mui/icons-material/Science"; import { Box, DialogProps, Stack } from "@mui/material"; import DropdownInput from "components/DropdownInput"; -import { EnteDrawer } from "components/EnteDrawer"; -import Titlebar from "components/Titlebar"; import { t } from "i18next"; -import { useState } from "react"; +import { AppContext } from "pages/_app"; +import { useContext, useState } from "react"; import AdvancedSettings from "./AdvancedSettings"; import MapSettings from "./MapSetting"; export default function Preferences({ open, onClose, onRootClose }) { + const appContext = useContext(AppContext); + const [advancedSettingsView, setAdvancedSettingsView] = useState(false); const [mapSettingsView, setMapSettingsView] = useState(false); + const [openMLSettings, setOpenMLSettings] = useState(false); const openAdvancedSettings = () => setAdvancedSettingsView(true); const closeAdvancedSettings = () => setAdvancedSettingsView(false); @@ -66,19 +75,45 @@ export default function Preferences({ open, onClose, onRootClose }) { endIcon={} label={t("ADVANCED")} /> + {isMLSupported && ( + + } + /> + + } + onClick={() => setOpenMLSettings(true)} + label={pt("ML search")} + /> + + + + )} - setOpenMLSettings(false)} + onRootClose={handleRootClose} + appContext={appContext} /> + ); } diff --git a/web/apps/photos/src/components/Sidebar/index.tsx b/web/apps/photos/src/components/Sidebar/index.tsx index 1dedaf6a79..9108d29d2e 100644 --- a/web/apps/photos/src/components/Sidebar/index.tsx +++ b/web/apps/photos/src/components/Sidebar/index.tsx @@ -1,4 +1,6 @@ import { openAccountsManagePasskeysPage } from "@/accounts/services/passkey"; +import { initiateEmail, openURL } from "@/new/photos/utils/web"; +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; import log from "@/next/log"; import { savedLogs } from "@/next/log-web"; import { customAPIHost } from "@/next/origins"; @@ -34,7 +36,6 @@ import { } from "@mui/material"; import Typography from "@mui/material/Typography"; import DeleteAccountModal from "components/DeleteAccountModal"; -import { EnteDrawer } from "components/EnteDrawer"; import TwoFactorModal from "components/TwoFactor/Modal"; import { WatchFolder } from "components/WatchFolder"; import LinkButton from "components/pages/gallery/LinkButton"; @@ -73,7 +74,6 @@ import { isSubscriptionCancelled, isSubscriptionPastDue, } from "utils/billing"; -import { openLink } from "utils/common"; import { getDownloadAppMessage } from "utils/ui"; import { isFamilyAdmin, isPartOfFamily } from "utils/user/family"; import { testUpload } from "../../../tests/upload.test"; @@ -594,10 +594,10 @@ const HelpSection: React.FC = () => { const { setDialogMessage } = useContext(AppContext); const { openExportModal } = useContext(GalleryContext); - const openRoadmap = () => - openLink("https://github.com/ente-io/ente/discussions", true); + const requestFeature = () => + openURL("https://github.com/ente-io/ente/discussions"); - const contactSupport = () => openLink("mailto:support@ente.io", true); + const contactSupport = () => initiateEmail("support@ente.io"); function openExport() { if (isElectron()) { @@ -610,7 +610,7 @@ const HelpSection: React.FC = () => { return ( <> diff --git a/web/apps/photos/src/components/Titlebar.tsx b/web/apps/photos/src/components/Titlebar.tsx deleted file mode 100644 index ed9089f4c2..0000000000 --- a/web/apps/photos/src/components/Titlebar.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { FlexWrapper } from "@ente/shared/components/Container"; -import ArrowBack from "@mui/icons-material/ArrowBack"; -import Close from "@mui/icons-material/Close"; -import { Box, IconButton, Typography } from "@mui/material"; - -interface Iprops { - title: string; - caption?: string; - onClose: () => void; - backIsClose?: boolean; - onRootClose?: () => void; - actionButton?: JSX.Element; -} - -export default function Titlebar({ - title, - caption, - onClose, - backIsClose, - actionButton, - onRootClose, -}: Iprops): JSX.Element { - return ( - <> - - - {backIsClose ? : } - - - {actionButton && actionButton} - {!backIsClose && ( - - - - )} - - - - - {title} - - - {caption} - - - - ); -} diff --git a/web/apps/photos/src/components/ml/MLSearchSettings.tsx b/web/apps/photos/src/components/ml/MLSearchSettings.tsx deleted file mode 100644 index 118684dccc..0000000000 --- a/web/apps/photos/src/components/ml/MLSearchSettings.tsx +++ /dev/null @@ -1,342 +0,0 @@ -import { - canEnableFaceIndexing, - disableML, - enableML, - isMLEnabled, -} from "@/new/photos/services/ml"; -import log from "@/next/log"; -import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; -import { - Box, - Button, - Checkbox, - DialogProps, - FormControlLabel, - FormGroup, - Link, - Stack, - Typography, -} from "@mui/material"; -import { EnteDrawer } from "components/EnteDrawer"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import Titlebar from "components/Titlebar"; -import { t } from "i18next"; -import { AppContext } from "pages/_app"; -import { useContext, useEffect, useState } from "react"; -import { Trans } from "react-i18next"; -import { - getFaceSearchEnabledStatus, - updateFaceSearchEnabledStatus, -} from "services/userService"; - -export const MLSearchSettings = ({ open, onClose, onRootClose }) => { - const { - setDialogMessage, - somethingWentWrong, - startLoading, - finishLoading, - } = useContext(AppContext); - - const [enableFaceSearchView, setEnableFaceSearchView] = useState(false); - - const openEnableFaceSearch = () => { - setEnableFaceSearchView(true); - }; - const closeEnableFaceSearch = () => { - setEnableFaceSearchView(false); - }; - - const enableMlSearch = async () => { - try { - const hasEnabledFaceSearch = await getFaceSearchEnabledStatus(); - if (!hasEnabledFaceSearch) { - openEnableFaceSearch(); - } else { - enableML(); - } - } catch (e) { - log.error("Enable ML search failed", e); - somethingWentWrong(); - } - }; - - const enableFaceSearch = async () => { - try { - startLoading(); - // Update the consent flag. - await updateFaceSearchEnabledStatus(true); - enableML(); - closeEnableFaceSearch(); - finishLoading(); - } catch (e) { - log.error("Enable face search failed", e); - somethingWentWrong(); - } - }; - - const disableMlSearch = async () => { - try { - disableML(); - onClose(); - } catch (e) { - log.error("Disable ML search failed", e); - somethingWentWrong(); - } - }; - - const disableFaceSearch = async () => { - try { - startLoading(); - await disableMlSearch(); - finishLoading(); - } catch (e) { - log.error("Disable face search failed", e); - somethingWentWrong(); - } - }; - - const confirmDisableFaceSearch = () => { - setDialogMessage({ - title: t("DISABLE_FACE_SEARCH_TITLE"), - content: ( - - - - ), - close: { text: t("CANCEL") }, - proceed: { - variant: "primary", - text: t("DISABLE_FACE_SEARCH"), - action: disableFaceSearch, - }, - }); - }; - - const handleRootClose = () => { - onClose(); - onRootClose(); - }; - - const handleDrawerClose: DialogProps["onClose"] = (_, reason) => { - if (reason === "backdropClick") { - handleRootClose(); - } else { - onClose(); - } - }; - - return ( - - - {isMLEnabled() ? ( - - ) : ( - - )} - - - - - ); -}; - -function EnableFaceSearch({ open, onClose, enableFaceSearch, onRootClose }) { - const [acceptTerms, setAcceptTerms] = useState(false); - - useEffect(() => { - setAcceptTerms(false); - }, [open]); - - const handleRootClose = () => { - onClose(); - onRootClose(); - }; - - const handleDrawerClose: DialogProps["onClose"] = (_, reason) => { - if (reason === "backdropClick") { - handleRootClose(); - } else { - onClose(); - } - }; - return ( - - - - - - - ), - }} - /> - - - - setAcceptTerms(e.target.checked) - } - /> - } - label={t("FACE_SEARCH_CONFIRMATION")} - /> - - - - - - - - - ); -} - -function EnableMLSearch({ onClose, enableMlSearch, onRootClose }) { - // const showDetails = () => - // openLink("https://ente.io/blog/desktop-ml-beta", true); - - const [canEnable, setCanEnable] = useState(false); - - useEffect(() => { - canEnableFaceIndexing().then((v) => setCanEnable(v)); - }, []); - - return ( - - - - {canEnable ? ( - - - {/* - - */} - - ) : ( - - {" "} - - {/* */} - We're putting finishing touches, coming back soon! - - - )} - - - ); -} - -function ManageMLSearch({ - onClose, - disableMlSearch, - handleDisableFaceSearch, - onRootClose, -}) { - return ( - - - - - - - - - - - - - - ); -} diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index e39ab8fb0e..423ad78d6c 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -1,6 +1,6 @@ import type { AccountsContextT } from "@/accounts/types/context"; import DownloadManager from "@/new/photos/services/download"; -import { initML } from "@/new/photos/services/ml"; +import { initML, isMLSupported } from "@/new/photos/services/ml"; import { clientPackageName, staticAppTitle } from "@/next/app"; import { CustomHead } from "@/next/components/Head"; import { setupI18n } from "@/next/i18n"; @@ -174,7 +174,7 @@ export default function App({ Component, pageProps }: AppProps) { } }; - initML(); + if (isMLSupported) initML(); electron.onOpenURL(handleOpenURL); electron.onAppUpdateAvailable(showUpdateDialog); diff --git a/web/apps/photos/src/services/searchService.ts b/web/apps/photos/src/services/searchService.ts index 4afd7af0ef..acf12a1e94 100644 --- a/web/apps/photos/src/services/searchService.ts +++ b/web/apps/photos/src/services/searchService.ts @@ -1,5 +1,5 @@ import { FILE_TYPE } from "@/media/file-type"; -import { faceIndexingStatus, isMLEnabled } from "@/new/photos/services/ml"; +import { isMLSupported, mlStatusSnapshot } from "@/new/photos/services/ml"; import type { Person } from "@/new/photos/services/ml/people"; import { EnteFile } from "@/new/photos/types/file"; import { isDesktop } from "@/next/app"; @@ -26,9 +26,7 @@ const DIGITS = new Set(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]); export const getDefaultOptions = async () => { return [ - // TODO-ML(MR): Skip this for now if indexing is disabled (eventually - // the indexing status should not be tied to results). - ...(isMLEnabled() ? [await getIndexStatusSuggestion()] : []), + await getMLStatusSuggestion(), ...(await convertSuggestionsToOptions(await getAllPeopleSuggestion())), ].filter((t) => !!t); }; @@ -171,37 +169,36 @@ export async function getAllPeopleSuggestion(): Promise> { } } -export async function getIndexStatusSuggestion(): Promise { - try { - const indexStatus = await faceIndexingStatus(); - - let label: string; - switch (indexStatus.phase) { - case "scheduled": - label = t("INDEXING_SCHEDULED"); - break; - case "indexing": - label = t("ANALYZING_PHOTOS", { - indexStatus, - }); - break; - case "clustering": - label = t("INDEXING_PEOPLE", { indexStatus }); - break; - case "done": - label = t("INDEXING_DONE", { indexStatus }); - break; - } - - return { - label, - type: SuggestionType.INDEX_STATUS, - value: indexStatus, - hide: true, - }; - } catch (e) { - log.error("getIndexStatusSuggestion failed", e); +export async function getMLStatusSuggestion(): Promise { + if (!isMLSupported) return undefined; + + const status = mlStatusSnapshot(); + + if (!status || status.phase == "disabled" || status.phase == "paused") + return undefined; + + let label: string; + switch (status.phase) { + case "scheduled": + label = t("INDEXING_SCHEDULED"); + break; + case "indexing": + label = t("ANALYZING_PHOTOS", { indexStatus: status }); + break; + case "clustering": + label = t("INDEXING_PEOPLE", { indexStatus: status }); + break; + case "done": + label = t("INDEXING_DONE", { indexStatus: status }); + break; } + + return { + label, + type: SuggestionType.INDEX_STATUS, + value: status, + hide: true, + }; } function getDateSuggestion(searchPhrase: string): Suggestion[] { diff --git a/web/apps/photos/src/services/sync.ts b/web/apps/photos/src/services/sync.ts index b628a3510d..5ebc5b43b0 100644 --- a/web/apps/photos/src/services/sync.ts +++ b/web/apps/photos/src/services/sync.ts @@ -1,6 +1,5 @@ import { fetchAndSaveFeatureFlagsIfNeeded } from "@/new/photos/services/feature-flags"; -import { triggerMLSync } from "@/new/photos/services/ml"; -import { isDesktop } from "@/next/app"; +import { isMLSupported, triggerMLSync } from "@/new/photos/services/ml"; import { syncEntities } from "services/entityService"; import { syncMapEnabled } from "services/userService"; @@ -17,7 +16,5 @@ export const sync = async () => { await syncEntities(); await syncMapEnabled(); fetchAndSaveFeatureFlagsIfNeeded(); - if (isDesktop) { - triggerMLSync(); - } + if (isMLSupported) triggerMLSync(); }; diff --git a/web/apps/photos/src/services/userService.ts b/web/apps/photos/src/services/userService.ts index 11da7b07c8..4c6e17e826 100644 --- a/web/apps/photos/src/services/userService.ts +++ b/web/apps/photos/src/services/userService.ts @@ -206,47 +206,6 @@ export const deleteAccount = async ( } }; -export const getFaceSearchEnabledStatus = async () => { - try { - const token = getToken(); - const resp: AxiosResponse = - await HTTPService.get( - await apiURL("/remote-store"), - { - key: "faceSearchEnabled", - defaultValue: false, - }, - { - "X-Auth-Token": token, - }, - ); - return resp.data.value === "true"; - } catch (e) { - log.error("failed to get face search enabled status", e); - throw e; - } -}; - -export const updateFaceSearchEnabledStatus = async (newStatus: boolean) => { - try { - const token = getToken(); - await HTTPService.post( - await apiURL("/remote-store/update"), - { - key: "faceSearchEnabled", - value: newStatus.toString(), - }, - null, - { - "X-Auth-Token": token, - }, - ); - } catch (e) { - log.error("failed to update face search enabled status", e); - throw e; - } -}; - export const syncMapEnabled = async () => { try { const status = await getMapEnabledStatus(); diff --git a/web/apps/photos/src/types/search/index.ts b/web/apps/photos/src/types/search/index.ts index 575cb0a9c7..130471ae76 100644 --- a/web/apps/photos/src/types/search/index.ts +++ b/web/apps/photos/src/types/search/index.ts @@ -1,5 +1,5 @@ import { FILE_TYPE } from "@/media/file-type"; -import type { FaceIndexingStatus } from "@/new/photos/services/ml"; +import type { MLStatus } from "@/new/photos/services/ml"; import type { Person } from "@/new/photos/services/ml/people"; import { EnteFile } from "@/new/photos/types/file"; import { City } from "services/locationSearchService"; @@ -31,7 +31,7 @@ export interface Suggestion { | DateValue | number[] | Person - | FaceIndexingStatus + | MLStatus | LocationTagData | City | FILE_TYPE diff --git a/web/apps/photos/src/utils/billing/index.ts b/web/apps/photos/src/utils/billing/index.ts index 302baa97ee..2afe4f395f 100644 --- a/web/apps/photos/src/utils/billing/index.ts +++ b/web/apps/photos/src/utils/billing/index.ts @@ -1,3 +1,4 @@ +import { openURL } from "@/new/photos/utils/web"; import log from "@/next/log"; import { SetDialogBoxAttributes } from "@ente/shared/components/DialogBox/types"; import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; @@ -8,7 +9,6 @@ import billingService from "services/billingService"; import { Plan, Subscription } from "types/billing"; import { SetLoading } from "types/gallery"; import { BonusData, UserDetails } from "types/user"; -import { openLink } from "utils/common"; import { getSubscriptionPurchaseSuccessMessage } from "utils/ui"; import { getTotalFamilyUsage, isPartOfFamily } from "utils/user/family"; @@ -220,7 +220,7 @@ export async function manageFamilyMethod( try { setLoading(true); const familyPortalRedirectURL = getRedirectURL(REDIRECTS.FAMILIES); - openLink(familyPortalRedirectURL, true); + openURL(familyPortalRedirectURL); } catch (e) { log.error("failed to redirect to family portal", e); setDialogMessage({ diff --git a/web/apps/photos/src/utils/common/index.ts b/web/apps/photos/src/utils/common/index.ts index 9de9f54c41..91628f98c4 100644 --- a/web/apps/photos/src/utils/common/index.ts +++ b/web/apps/photos/src/utils/common/index.ts @@ -6,16 +6,6 @@ export const preloadImage = (imgBasePath: string) => { new Image().srcset = srcSet.join(","); }; -export function openLink(href: string, newTab?: boolean) { - const a = document.createElement("a"); - a.href = href; - if (newTab) { - a.target = "_blank"; - } - a.rel = "noreferrer noopener"; - a.click(); -} - export function isClipboardItemPresent() { return typeof ClipboardItem !== "undefined"; } diff --git a/web/apps/photos/src/utils/ui/index.tsx b/web/apps/photos/src/utils/ui/index.tsx index c930f47c8c..f8c0d6cb12 100644 --- a/web/apps/photos/src/utils/ui/index.tsx +++ b/web/apps/photos/src/utils/ui/index.tsx @@ -1,3 +1,4 @@ +import { openURL } from "@/new/photos/utils/web"; import { ensureElectron } from "@/next/electron"; import { AppUpdate } from "@/next/types/ipc"; import { DialogBoxAttributes } from "@ente/shared/components/DialogBox/types"; @@ -7,7 +8,6 @@ import { Link } from "@mui/material"; import { t } from "i18next"; import { Trans } from "react-i18next"; import { Subscription } from "types/billing"; -import { openLink } from "utils/common"; export const getDownloadAppMessage = (): DialogBoxAttributes => { return { @@ -25,7 +25,7 @@ export const getDownloadAppMessage = (): DialogBoxAttributes => { }; }; -const downloadApp = () => openLink("https://ente.io/download/desktop", true); +const downloadApp = () => openURL("https://ente.io/download/desktop"); export const getTrashFilesMessage = ( deleteFileHelper, diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 84c71b09c5..7919314068 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -71,8 +71,11 @@ const Contents: React.FC = (props) => { () => void getKV("apiOrigin").then((o) => setInitialAPIOrigin( - // TODO: Migration of apiOrigin from local storage to indexed DB - // Remove me after a bit (27 June 2024). + // Migrate apiOrigin from local storage to indexed DB. + // + // This code was added 27 June 2024. Note that the legacy + // value was never in production builds, only nightlies, so + // this code can be removed soon (tag: Migration). o ?? localStorage.getItem("apiOrigin") ?? "", ), ), @@ -215,8 +218,11 @@ const Form: React.FC = ({ initialAPIOrigin, onClose }) => { const updateAPIOrigin = async (origin: string) => { if (!origin) { await removeKV("apiOrigin"); - // TODO: Migration of apiOrigin from local storage to indexed DB - // Remove me after a bit (27 June 2024). + // Migrate apiOrigin from local storage to indexed DB. + // + // This code was added 27 June 2024. Note that the legacy value was + // never in production builds, only nightlies, so this code can be + // removed at some point soon (tag: Migration). localStorage.removeItem("apiOrigin"); return; } diff --git a/web/packages/new/photos/components/MLSettings.tsx b/web/packages/new/photos/components/MLSettings.tsx new file mode 100644 index 0000000000..3adb4b6a0b --- /dev/null +++ b/web/packages/new/photos/components/MLSettings.tsx @@ -0,0 +1,416 @@ +import { + disableML, + enableML, + getIsMLEnabledRemote, + isMLEnabled, + mlStatusSnapshot, + mlStatusSubscribe, + pauseML, + resumeML, + type MLStatus, +} from "@/new/photos/services/ml"; +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { MenuItemGroup } from "@/new/shared/components/Menu"; +import { Titlebar } from "@/new/shared/components/Titlebar"; +import { pt } from "@/next/i18n"; +import log from "@/next/log"; +import EnteSpinner from "@ente/shared/components/EnteSpinner"; +import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; +import { + Box, + Button, + Checkbox, + Divider, + FormControlLabel, + FormGroup, + Link, + Paper, + Stack, + Typography, + type DialogProps, +} from "@mui/material"; +import { t } from "i18next"; +import React, { useEffect, useState, useSyncExternalStore } from "react"; +import { Trans } from "react-i18next"; +import type { NewAppContextPhotos } from "../types/context"; +import { openURL } from "../utils/web"; + +interface MLSettingsProps { + /** If `true`, then this drawer page is shown. */ + open: boolean; + /** Called when the user wants to go back from this drawer page. */ + onClose: () => void; + /** Called when the user wants to close the entire stack of drawers. */ + onRootClose: () => void; + /** See: [Note: Migrating components that need the app context]. */ + appContext: NewAppContextPhotos; +} + +export const MLSettings: React.FC = ({ + open, + onClose, + onRootClose, + appContext, +}) => { + const { + startLoading, + finishLoading, + setDialogBoxAttributesV2, + somethingWentWrong, + } = appContext; + + const mlStatus = useSyncExternalStore(mlStatusSubscribe, mlStatusSnapshot); + const [openFaceConsent, setOpenFaceConsent] = useState(false); + + const handleRootClose = () => { + onClose(); + onRootClose(); + }; + + const handleDrawerClose: DialogProps["onClose"] = (_, reason) => { + if (reason == "backdropClick") handleRootClose(); + else onClose(); + }; + + const handleEnableML = async () => { + startLoading(); + try { + if (!(await getIsMLEnabledRemote())) { + setOpenFaceConsent(true); + } else { + await enableML(); + } + } catch (e) { + log.error("Failed to enable or resume ML", e); + somethingWentWrong(); + } finally { + finishLoading(); + } + }; + + const handleConsent = async () => { + startLoading(); + try { + await enableML(); + // Close the FaceConsent drawer, come back to ourselves. + setOpenFaceConsent(false); + } catch (e) { + log.error("Failed to enable ML", e); + somethingWentWrong(); + } finally { + finishLoading(); + } + }; + + const handleDisableML = async () => { + startLoading(); + try { + await disableML(); + } catch (e) { + log.error("Failed to disable ML", e); + somethingWentWrong(); + } finally { + finishLoading(); + } + }; + + let component: React.ReactNode; + if (!mlStatus) { + component = ; + } else if (mlStatus.phase == "disabled") { + component = ; + } else { + component = ( + + ); + } + + return ( + + + + + {component} + + + + setOpenFaceConsent(false)} + onRootClose={handleRootClose} + onConsent={handleConsent} + /> + + ); +}; + +const Loading: React.FC = () => { + return ( + + + + ); +}; + +interface EnableMLProps { + /** Called when the user enables ML. */ + onEnable: () => void; +} + +const EnableML: React.FC = ({ onEnable }) => { + // TODO-ML: Update link. + const moreDetails = () => openURL("https://ente.io/blog/desktop-ml-beta"); + + return ( + + + {pt( + "Enable ML (Machine Learning) for face recognition, magic search and other advanced search features", + )} + + + + + + + + {pt( + 'Magic search allows to search photos by their contents (e.g. "car", "red car" or even "ferrari")', + )} + + + ); +}; + +type FaceConsentProps = Omit & { + /** Called when the user provides their consent. */ + onConsent: () => void; +}; + +const FaceConsent: React.FC = ({ + open, + onClose, + onRootClose, + onConsent, +}) => { + const [acceptTerms, setAcceptTerms] = useState(false); + + useEffect(() => { + setAcceptTerms(false); + }, [open]); + + const handleRootClose = () => { + onClose(); + onRootClose(); + }; + + const handleDrawerClose: DialogProps["onClose"] = (_, reason) => { + if (reason == "backdropClick") handleRootClose(); + else onClose(); + }; + + return ( + + + + + + + ), + }} + /> + + + + setAcceptTerms(e.target.checked) + } + /> + } + label={t("FACE_SEARCH_CONFIRMATION")} + /> + + + + + + + + + ); +}; + +interface ManageMLProps { + /** The {@link MLStatus}; a non-disabled one. */ + mlStatus: Exclude; + /** Called when the user wants to disable ML. */ + onDisableML: () => void; + /** Subset of appContext. */ + setDialogBoxAttributesV2: NewAppContextPhotos["setDialogBoxAttributesV2"]; +} + +const ManageML: React.FC = ({ + mlStatus, + onDisableML, + setDialogBoxAttributesV2, +}) => { + const { phase, nSyncedFiles, nTotalFiles } = mlStatus; + + let status: string; + switch (phase) { + case "paused": + status = pt("Paused"); + break; + case "indexing": + status = pt("Indexing"); + break; + case "scheduled": + status = pt("Scheduled"); + break; + // TODO: Clustering + default: + status = pt("Done"); + break; + } + const processed = `${nSyncedFiles} / ${nTotalFiles}`; + + const handleToggleLocal = () => (isMLEnabled() ? pauseML() : resumeML()); + + const confirmDisableML = () => { + setDialogBoxAttributesV2({ + title: pt("Disable ML search"), + content: ( + + {pt( + "Do you want to disable ML search on all your devices?", + )} + + ), + close: { text: t("CANCEL") }, + proceed: { + variant: "critical", + text: pt("Disable"), + action: onDisableML, + }, + buttonDirection: "row", + }); + }; + + return ( + + + + + + + + + + + + + + {pt("Status")} + + {status} + + + + + {pt("Processed")} + + {processed} + + + + + ); +}; diff --git a/web/packages/new/photos/components/MLSettingsBeta.tsx b/web/packages/new/photos/components/MLSettingsBeta.tsx new file mode 100644 index 0000000000..8e50a6119a --- /dev/null +++ b/web/packages/new/photos/components/MLSettingsBeta.tsx @@ -0,0 +1,60 @@ +import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; +import { Titlebar } from "@/new/shared/components/Titlebar"; +import { pt, ut } from "@/next/i18n"; +import { Box, Stack, Typography, type DialogProps } from "@mui/material"; +import React from "react"; + +interface MLSettingsBetaProps { + /** If `true`, then this drawer page is shown. */ + open: boolean; + /** Called when the user wants to go back from this drawer page. */ + onClose: () => void; + /** Called when the user wants to close the entire stack of drawers. */ + onRootClose: () => void; +} + +export const MLSettingsBeta: React.FC = ({ + open, + onClose, + onRootClose, +}) => { + const handleRootClose = () => { + onClose(); + onRootClose(); + }; + + const handleDrawerClose: DialogProps["onClose"] = (_, reason) => { + if (reason == "backdropClick") handleRootClose(); + else onClose(); + }; + + return ( + + + + + + + + {ut( + "We're putting finishing touches, coming back soon!", + )} + + + + + + ); +}; diff --git a/web/apps/photos/src/components/ml/PeopleList.tsx b/web/packages/new/photos/components/PeopleList.tsx similarity index 84% rename from web/apps/photos/src/components/ml/PeopleList.tsx rename to web/packages/new/photos/components/PeopleList.tsx index 2a3da9f3b0..5c13aa9c55 100644 --- a/web/apps/photos/src/components/ml/PeopleList.tsx +++ b/web/packages/new/photos/components/PeopleList.tsx @@ -3,41 +3,37 @@ import { unidentifiedFaceIDs, } from "@/new/photos/services/ml"; import type { Person } from "@/new/photos/services/ml/people"; -import { EnteFile } from "@/new/photos/types/file"; +import type { EnteFile } from "@/new/photos/types/file"; import { blobCache } from "@/next/blob-cache"; import { Skeleton, Typography, styled } from "@mui/material"; import { t } from "i18next"; import React, { useEffect, useState } from "react"; export interface PeopleListProps { - people: Array; - maxRows?: number; + people: Person[]; + maxRows: number; onSelect?: (person: Person, index: number) => void; } -export const PeopleList = React.memo((props: PeopleListProps) => { +export const PeopleList: React.FC = ({ + people, + maxRows, + onSelect, +}) => { return ( - - {props.people.map((person, index) => ( + + {people.map((person, index) => ( - props.onSelect && props.onSelect(person, index) - } + clickable={!!onSelect} + onClick={() => onSelect && onSelect(person, index)} > ))} ); -}); +}; const FaceChipContainer = styled("div")` display: flex; @@ -89,7 +85,7 @@ export const UnidentifiedFaces: React.FC = ({ useEffect(() => { let didCancel = false; - (async () => { + const go = async () => { const faceIDs = await unidentifiedFaceIDs(enteFile); !didCancel && setFaceIDs(faceIDs); // Don't block for the regeneration to happen. If anything got @@ -99,7 +95,9 @@ export const UnidentifiedFaces: React.FC = ({ void regenerateFaceCropsIfNeeded(enteFile).then((r) => setDidRegen(r), ); - })(); + }; + + void go(); return () => { didCancel = true; @@ -140,7 +138,7 @@ const FaceCropImageView: React.FC = ({ faceID }) => { useEffect(() => { let didCancel = false; if (faceID) { - blobCache("face-crops") + void blobCache("face-crops") .then((cache) => cache.get(faceID)) .then((data) => { if (data) { @@ -154,6 +152,9 @@ const FaceCropImageView: React.FC = ({ faceID }) => { didCancel = true; if (objectURL) URL.revokeObjectURL(objectURL); }; + // TODO: The linter warning is actually correct, objectURL should be a + // dependency, but adding that require reworking this code first. + // eslint-disable-next-line react-hooks/exhaustive-deps }, [faceID]); return objectURL ? ( diff --git a/web/packages/new/photos/services/ml/bitmap.ts b/web/packages/new/photos/services/ml/bitmap.ts index 3772385425..7b851d2b8c 100644 --- a/web/packages/new/photos/services/ml/bitmap.ts +++ b/web/packages/new/photos/services/ml/bitmap.ts @@ -7,7 +7,7 @@ import { renderableImageBlob } from "../../utils/file"; import { readStream } from "../../utils/native-stream"; import DownloadManager from "../download"; import type { UploadItem } from "../upload/types"; -import type { MLWorkerElectron } from "./worker-electron"; +import type { MLWorkerElectron } from "./worker-types"; export interface ImageBitmapAndData { bitmap: ImageBitmap; @@ -35,22 +35,14 @@ export const imageBitmapAndData = async ( ? await renderableUploadItemImageBitmap(enteFile, uploadItem, electron) : await renderableImageBitmap(enteFile); - // Use an OffscreenCanvas to get the bitmap's data. - const { width, height } = imageBitmap; + // Use an OffscreenCanvas to get the bitmap's data. const offscreenCanvas = new OffscreenCanvas(width, height); const ctx = ensure(offscreenCanvas.getContext("2d")); ctx.drawImage(imageBitmap, 0, 0, width, height); const imageData = ctx.getImageData(0, 0, width, height); - // TODO-ML: This check isn't needed, keeping it around during scaffolding. - if ( - imageBitmap.width != imageData.width || - imageBitmap.height != imageData.height - ) - throw new Error("Dimension mismatch"); - return { bitmap: imageBitmap, data: imageData }; }; diff --git a/web/packages/new/photos/services/ml/clip.ts b/web/packages/new/photos/services/ml/clip.ts index 22fd05ec80..16bf637e5a 100644 --- a/web/packages/new/photos/services/ml/clip.ts +++ b/web/packages/new/photos/services/ml/clip.ts @@ -3,8 +3,8 @@ import type { Electron } from "@/next/types/ipc"; import type { ImageBitmapAndData } from "./bitmap"; import { clipIndexes } from "./db"; import { pixelRGBBicubic } from "./image"; -import { cosineSimilarity, norm } from "./math"; -import type { MLWorkerElectron } from "./worker-electron"; +import { dotProduct, norm } from "./math"; +import type { MLWorkerElectron } from "./worker-types"; /** * The version of the CLIP indexing pipeline implemented by the current client. @@ -26,7 +26,7 @@ export const clipIndexingVersion = 1; * trained) encoders - one for images, and one for text - that both map to the * same embedding space. * - * We use this for natural language search within the app: + * We use this for natural language search (aka "magic search") within the app: * * 1. Pre-compute an embedding for each image. * @@ -202,7 +202,12 @@ export const clipMatches = async ( const textEmbedding = normalized(t); const items = (await clipIndexes()).map( ({ fileID, embedding }) => - [fileID, cosineSimilarity(embedding, textEmbedding)] as const, + // What we want to do is `cosineSimilarity`, but since both the + // embeddings involved are already normalized, we can save the norm + // calculations and directly do their `dotProduct`. + // + // This code is on the hot path, so these optimizations help. + [fileID, dotProduct(embedding, textEmbedding)] as const, ); return new Map(items.filter(([, score]) => score >= 0.23)); }; diff --git a/web/packages/new/photos/services/ml/db.ts b/web/packages/new/photos/services/ml/db.ts index 9e4f9cf90b..51ca2f749b 100644 --- a/web/packages/new/photos/services/ml/db.ts +++ b/web/packages/new/photos/services/ml/db.ts @@ -135,14 +135,14 @@ const openMLDB = async () => { const deleteLegacyDB = () => { // Delete the legacy face DB v1. // - // This code was added June 2024 (v1.7.1-rc) and can be removed once clients - // have migrated over. + // This code was added June 2024 (v1.7.1-rc) and can be removed at some + // point when most clients have migrated (tag: Migration). void deleteDB("mldata"); // Delete the legacy CLIP (mostly) related keys from LocalForage. // - // This code was added July 2024 (v1.7.2-rc) and can be removed once - // sufficient clients have migrated over (tag: Migration). + // This code was added July 2024 (v1.7.2-rc) and can be removed at some + // point when most clients have migrated (tag: Migration). void Promise.all([ localForage.removeItem("embeddings"), localForage.removeItem("embedding_sync_time"), diff --git a/web/packages/new/photos/services/ml/embedding.ts b/web/packages/new/photos/services/ml/embedding.ts index eadeb00b0e..a6884bc17d 100644 --- a/web/packages/new/photos/services/ml/embedding.ts +++ b/web/packages/new/photos/services/ml/embedding.ts @@ -1,12 +1,12 @@ -import { - decryptFileMetadata, - encryptFileMetadata, -} from "@/new/common/crypto/ente"; import { getAllLocalFiles, getLocalTrashedFiles, } from "@/new/photos/services/files"; import type { EnteFile } from "@/new/photos/types/file"; +import { + decryptFileMetadata, + encryptFileMetadata, +} from "@/new/shared/crypto/ente"; import { authenticatedRequestHeaders, ensureOk } from "@/next/http"; import { getKV, setKV } from "@/next/kv"; import log from "@/next/log"; diff --git a/web/packages/new/photos/services/ml/face.ts b/web/packages/new/photos/services/ml/face.ts index 5ecd57d771..4e18127a2c 100644 --- a/web/packages/new/photos/services/ml/face.ts +++ b/web/packages/new/photos/services/ml/face.ts @@ -26,7 +26,7 @@ import { warpAffineFloat32List, } from "./image"; import { clamp } from "./math"; -import type { MLWorkerElectron } from "./worker-electron"; +import type { MLWorkerElectron } from "./worker-types"; /** * The version of the face indexing pipeline implemented by the current client. diff --git a/web/packages/new/photos/services/ml/index.ts b/web/packages/new/photos/services/ml/index.ts index f26357d48d..64571f1de0 100644 --- a/web/packages/new/photos/services/ml/index.ts +++ b/web/packages/new/photos/services/ml/index.ts @@ -3,10 +3,6 @@ */ import { FILE_TYPE } from "@/media/file-type"; -import { - isBetaUser, - isInternalUser, -} from "@/new/photos/services/feature-flags"; import type { EnteFile } from "@/new/photos/types/file"; import { isDesktop } from "@/next/app"; import { blobCache } from "@/next/blob-cache"; @@ -14,27 +10,51 @@ import { ensureElectron } from "@/next/electron"; import log from "@/next/log"; import { ComlinkWorker } from "@/next/worker/comlink-worker"; import { proxy } from "comlink"; +import { isBetaUser, isInternalUser } from "../feature-flags"; +import { getRemoteFlag, updateRemoteFlag } from "../remote-store"; import type { UploadItem } from "../upload/types"; import { regenerateFaceCrops } from "./crop"; import { clearMLDB, faceIndex, indexableAndIndexedCounts } from "./db"; import { MLWorker } from "./worker"; /** - * In-memory flag that tracks if ML is enabled. + * In-memory flag that tracks if ML is enabled locally. * - * - On app start, this is read from local storage in the `initML` function. + * - On app start, this is read from local storage during {@link initML}. * - * - If the user updates their preference, then `setMLEnabled` will get called - * with the updated preference where this value will be updated (in addition - * to updating local storage). + * - It gets updated if the user enables/disables ML (remote) or if they + * pause/resume ML (local). * - * - It is cleared in `logoutML`. + * - It is cleared in {@link logoutML}. */ -let _isMLEnabled = false; +let _isMLEnabledLocal = false; + +/** + * In-memory flag that tracks if the remote flag for ML is set. + * + * - It is updated each time we sync the status with remote. + * + * - It is cleared in {@link logoutML}. + */ +let _isMLEnabledRemote: boolean | undefined; /** Cached instance of the {@link ComlinkWorker} that wraps our web worker. */ let _comlinkWorker: ComlinkWorker | undefined; +/** + * Subscriptions to {@link MLStatus}. + * + * See {@link mlStatusSubscribe}. + */ +let _mlStatusListeners: (() => void)[] = []; + +/** + * Snapshot of {@link MLStatus}. + * + * See {@link mlStatusSnapshot}. + */ +let _mlStatusSnapshot: MLStatus | undefined; + /** Lazily created, cached, instance of {@link MLWorker}. */ const worker = async () => { if (!_comlinkWorker) _comlinkWorker = await createComlinkWorker(); @@ -49,12 +69,17 @@ const createComlinkWorker = async () => { computeFaceEmbeddings: electron.computeFaceEmbeddings, computeCLIPImageEmbedding: electron.computeCLIPImageEmbedding, }; + const delegate = { + workerDidProcessFile, + }; const cw = new ComlinkWorker( "ML", new Worker(new URL("worker.ts", import.meta.url)), ); - await cw.remote.then((w) => w.init(proxy(mlWorkerElectron))); + await cw.remote.then((w) => + w.init(proxy(mlWorkerElectron), proxy(delegate)), + ); return cw; }; @@ -64,6 +89,8 @@ const createComlinkWorker = async () => { * This is useful during logout to immediately stop any background ML operations * that are in-flight for the current user. After the user logs in again, a new * {@link worker} will be created on demand for subsequent usage. + * + * It is also called when the user pauses or disables ML. */ export const terminateMLWorker = () => { if (_comlinkWorker) { @@ -72,14 +99,28 @@ export const terminateMLWorker = () => { } }; +/** + * Return true if the current client supports ML. + * + * ML currently only works when we're running in our desktop app. + */ +// TODO-ML: +export const isMLSupported = + isDesktop && process.env.NEXT_PUBLIC_ENTE_ENABLE_WIP_ML; + +/** + * Was this someone who might've enabled the beta ML? If so, show them the + * coming back soon banner while we finalize it. + * TODO-ML: + */ +export const canEnableML = async () => + (await isInternalUser()) || (await isBetaUser()); + /** * Initialize the ML subsystem if the user has enabled it in preferences. */ export const initML = () => { - // ML currently only works when we're running in our desktop app. - if (!isDesktop) return; - // TODO-ML: Rename the isFace* flag since it now drives ML as a whole. - _isMLEnabled = isFaceIndexingEnabled(); + _isMLEnabledLocal = isMLEnabledLocally(); }; export const logoutML = async () => { @@ -87,83 +128,149 @@ export const logoutML = async () => { // reasons mentioned in [Note: Caching IDB instances in separate execution // contexts], it gets called first in the logout sequence, and then this // function (`logoutML`) gets called at a later point in time. - _isMLEnabled = false; + _isMLEnabledLocal = false; + _isMLEnabledRemote = undefined; + _mlStatusListeners = []; + _mlStatusSnapshot = undefined; await clearMLDB(); }; -/** - * Return true if we should show an option to the user to allow them to enable - * face search in the UI. - */ -export const canEnableFaceIndexing = async () => - (await isInternalUser()) || (await isBetaUser()); - /** * Return true if the user has enabled machine learning in their preferences. * - * TODO-ML: The UI for this needs rework. We might retain the older remote (and - * local) storage key, but otherwise this setting now reflects the state of ML - * overall and not just face search. + * [Note: ML preferences] + * + * The user may enable ML. This enables in both locally by persisting a local + * storage flag, and sets a flag on remote so that the user's other devices can + * also enable it if they wish. + * + * The user may pause ML locally. This does not modify the remote flag, but it + * unsets the local flag. Subsequently resuming ML (locally) will set the local + * flag again. + * + * ML related operations are driven by the {@link isMLEnabled} property. This is + * true if ML is enabled locally (which implies it is also enabled on remote). */ export const isMLEnabled = () => - // Impl note: Keep it fast, the UI directly calls this multiple times. - _isMLEnabled; + // Implementation note: Keep it fast, it might be called frequently. + _isMLEnabledLocal; /** * Enable ML. * - * Persist the user's preference and trigger a sync. + * Persist the user's preference both locally and on remote, and trigger a sync. */ -export const enableML = () => { - setIsFaceIndexingEnabled(true); - _isMLEnabled = true; +export const enableML = async () => { + await updateIsMLEnabledRemote(true); + setIsMLEnabledLocally(true); + _isMLEnabledRemote = true; + _isMLEnabledLocal = true; + setInterimScheduledStatus(); + triggerStatusUpdate(); triggerMLSync(); }; /** * Disable ML. * - * Stop any in-progress ML tasks and persist the user's preference. + * Stop any in-progress ML tasks, and persist the user's preference both locally + * and on remote. + */ +export const disableML = async () => { + await updateIsMLEnabledRemote(false); + terminateMLWorker(); + setIsMLEnabledLocally(false); + _isMLEnabledRemote = false; + _isMLEnabledLocal = false; + triggerStatusUpdate(); +}; + +/** + * Pause ML on this device. + * + * Stop any in-progress ML tasks, and persist the user's local preference. */ -export const disableML = () => { +export const pauseML = () => { terminateMLWorker(); - setIsFaceIndexingEnabled(false); - _isMLEnabled = false; + setIsMLEnabledLocally(false); + _isMLEnabledLocal = false; + triggerStatusUpdate(); }; /** - * Return true if the user has enabled face indexing in the app's settings. + * Resume ML on this device. + * + * Persist the user's preference locally, and trigger a sync. + */ +export const resumeML = () => { + setIsMLEnabledLocally(true); + _isMLEnabledLocal = true; + setInterimScheduledStatus(); + triggerStatusUpdate(); + triggerMLSync(); +}; + +/** + * Return true if ML is enabled locally. + * + * This setting is persisted locally (in local storage). It is not synced with + * remote and only tracks if ML is enabled locally. * - * This setting is persisted locally (in local storage) and is not synced with - * remote. There is a separate setting, "faceSearchEnabled" that is synced with - * remote, but that tracks whether or not the user has enabled face search once - * on any client. This {@link isFaceIndexingEnabled} property, on the other - * hand, denotes whether or not indexing is enabled on the current client. + * The remote status is tracked with a separate {@link isMLEnabledRemote} flag + * that is synced with remote. */ -const isFaceIndexingEnabled = () => +const isMLEnabledLocally = () => localStorage.getItem("faceIndexingEnabled") == "1"; /** - * Update the (locally stored) value of {@link isFaceIndexingEnabled}. + * Update the (locally stored) value of {@link isMLEnabledLocally}. */ -const setIsFaceIndexingEnabled = (enabled: boolean) => +const setIsMLEnabledLocally = (enabled: boolean) => enabled ? localStorage.setItem("faceIndexingEnabled", "1") : localStorage.removeItem("faceIndexingEnabled"); +/** + * For historical reasons, this is called "faceSearchEnabled" (it started off as + * a flag to ensure we have taken the face recognition consent from the user). + * + * Now it tracks the status of ML in general (which includes faces + consent). + */ +const mlRemoteKey = "faceSearchEnabled"; + +/** + * Return `true` if the flag to enable ML is set on remote. + */ +export const getIsMLEnabledRemote = () => getRemoteFlag(mlRemoteKey); + +/** + * Update the remote flag that tracks ML status across the user's devices. + */ +const updateIsMLEnabledRemote = (enabled: boolean) => + updateRemoteFlag(mlRemoteKey, enabled); + /** * Trigger a "sync", whatever that means for the ML subsystem. * - * This is called during the global sync sequence. If ML is enabled, then we use - * this as a signal to pull embeddings from remote, and start backfilling if - * needed. + * This is called during the global sync sequence. + * + * First we check again with remote ML flag is set. If it is not set, then we + * disable ML locally too. + * + * Otherwise, and if ML is enabled locally also, then we use this as a signal to + * pull embeddings from remote, and start backfilling if needed. * * This function does not wait for these processes to run to completion, and * returns immediately. */ -export const triggerMLSync = () => { - if (!_isMLEnabled) return; - void worker().then((w) => w.sync()); +export const triggerMLSync = () => void mlSync(); + +const mlSync = async () => { + _isMLEnabledRemote = await getIsMLEnabledRemote(); + if (!_isMLEnabledRemote) _isMLEnabledLocal = false; + triggerStatusUpdate(); + + if (_isMLEnabledLocal) void worker().then((w) => w.sync()); }; /** @@ -182,51 +289,109 @@ export const triggerMLSync = () => { * image part of the live photo that was uploaded. */ export const indexNewUpload = (enteFile: EnteFile, uploadItem: UploadItem) => { - if (!_isMLEnabled) return; + if (!_isMLEnabledLocal) return; if (enteFile.metadata.fileType !== FILE_TYPE.IMAGE) return; log.debug(() => ["ml/liveq", { enteFile, uploadItem }]); void worker().then((w) => w.onUpload(enteFile, uploadItem)); }; -export interface FaceIndexingStatus { - /** - * Which phase we are in within the indexing pipeline when viewed across the - * user's entire library: - * - * - "scheduled": There are files we know of that have not been indexed. - * - * - "indexing": The face indexer is currently running. - * - * - "clustering": All files we know of have been indexed, and we are now - * clustering the faces that were found. - * - * - "done": Face indexing and clustering is complete for the user's - * library. - */ - phase: "scheduled" | "indexing" | "clustering" | "done"; - /** The number of files that have already been indexed. */ - nSyncedFiles: number; - /** The total number of files that are eligible for indexing. */ - nTotalFiles: number; -} +export type MLStatus = + | { phase: "disabled" /* The ML remote flag is off */ } + | { + /** + * Which phase we are in within the indexing pipeline when viewed across the + * user's entire library: + * + * - "paused": ML is currently paused on this device. + * + * - "scheduled": There are files we know of that have not been indexed. + * + * - "indexing": The indexer is currently running. + * + * - "clustering": All file we know of have been indexed, and we are now + * clustering the faces that were found. + * + * - "done": ML indexing and face clustering is complete for the user's + * library. + */ + phase: "paused" | "scheduled" | "indexing" | "clustering" | "done"; + /** The number of files that have already been indexed. */ + nSyncedFiles: number; + /** The total number of files that are eligible for indexing. */ + nTotalFiles: number; + }; + +/** + * A function that can be used to subscribe to updates in the ML status. + * + * This, along with {@link mlStatusSnapshot}, is meant to be used as arguments + * to React's {@link useSyncExternalStore}. + * + * @param callback A function that will be invoked whenever the result of + * {@link mlStatusSnapshot} changes. + * + * @returns A function that can be used to clear the subscription. + */ +export const mlStatusSubscribe = (onChange: () => void): (() => void) => { + _mlStatusListeners.push(onChange); + return () => { + _mlStatusListeners = _mlStatusListeners.filter((l) => l != onChange); + }; +}; + +/** + * Return the last known, cached {@link MLStatus}. + * + * This, along with {@link mlStatusSnapshot}, is meant to be used as arguments + * to React's {@link useSyncExternalStore}. + * + * A return value of `undefined` indicates that we're still performing the + * asynchronous tasks that are needed to get the status. + */ +export const mlStatusSnapshot = (): MLStatus | undefined => { + const result = _mlStatusSnapshot; + // We don't have it yet, trigger an update. + if (!result) triggerStatusUpdate(); + return result; +}; + +/** + * Trigger an asynchronous and unconditional update of the {@link MLStatus} + * snapshot. + */ +const triggerStatusUpdate = () => void updateMLStatusSnapshot(); + +/** Unconditionally update of the {@link MLStatus} snapshot. */ +const updateMLStatusSnapshot = async () => + setMLStatusSnapshot(await getMLStatus()); + +const setMLStatusSnapshot = (snapshot: MLStatus) => { + _mlStatusSnapshot = snapshot; + _mlStatusListeners.forEach((l) => l()); +}; /** - * Return the current state of the face indexing pipeline. + * Return the current state of the ML subsystem. * - * Precondition: ML must be enabled. + * Precondition: ML must be enabled on remote, though it is fine if it is paused + * locally. */ -export const faceIndexingStatus = async (): Promise => { - if (!isMLEnabled()) - throw new Error("Cannot get indexing status when ML is not enabled"); +const getMLStatus = async (): Promise => { + if (!_isMLEnabledRemote) return { phase: "disabled" }; const { indexedCount, indexableCount } = await indexableAndIndexedCounts(); - const isIndexing = await (await worker()).isIndexing(); - let phase: FaceIndexingStatus["phase"]; - if (indexableCount > 0) { - phase = !isIndexing ? "scheduled" : "indexing"; + let phase: MLStatus["phase"]; + if (!_isMLEnabledLocal) { + phase = "paused"; } else { - phase = "done"; + const isIndexing = await (await worker()).isIndexing(); + + if (indexableCount > 0) { + phase = !isIndexing ? "scheduled" : "indexing"; + } else { + phase = "done"; + } } return { @@ -236,6 +401,28 @@ export const faceIndexingStatus = async (): Promise => { }; }; +/** + * When the user enables or resumes ML, we wish to give immediate feedback. + * + * So this is an intermediate state with possibly incorrect counts (but correct + * phase) that is set immediately to trigger a UI update. It uses the counts + * from the last known status, just updates the phase. + * + * Once the worker is initialized and the correct counts fetched, this will + * update to the correct state (should take less than one second). + */ +const setInterimScheduledStatus = () => { + let nSyncedFiles = 0, + nTotalFiles = 0; + if (_mlStatusSnapshot && _mlStatusSnapshot.phase != "disabled") { + nSyncedFiles = _mlStatusSnapshot.nSyncedFiles; + nTotalFiles = _mlStatusSnapshot.nTotalFiles; + } + setMLStatusSnapshot({ phase: "scheduled", nSyncedFiles, nTotalFiles }); +}; + +const workerDidProcessFile = triggerStatusUpdate; + /** * Return the IDs of all the faces in the given {@link enteFile} that are not * associated with a person cluster. diff --git a/web/packages/new/photos/services/ml/people.ts b/web/packages/new/photos/services/ml/people.ts index 0dac096dc4..97118ea94a 100644 --- a/web/packages/new/photos/services/ml/people.ts +++ b/web/packages/new/photos/services/ml/people.ts @@ -2,7 +2,7 @@ export interface Person { id: number; name?: string; files: number[]; - displayFaceId?: string; + displayFaceId: string; } // TODO-ML(MR): Forced disable clustering. It doesn't currently work, diff --git a/web/packages/new/photos/services/ml/worker-electron.ts b/web/packages/new/photos/services/ml/worker-types.ts similarity index 57% rename from web/packages/new/photos/services/ml/worker-electron.ts rename to web/packages/new/photos/services/ml/worker-types.ts index 0e187a178a..2e30703844 100644 --- a/web/packages/new/photos/services/ml/worker-electron.ts +++ b/web/packages/new/photos/services/ml/worker-types.ts @@ -1,3 +1,8 @@ +/** + * @file Type for the objects shared (as a Comlink proxy) by the main thread and + * the ML worker. + */ + /** * A subset of {@link Electron} provided to the {@link MLWorker}. * @@ -12,3 +17,16 @@ export interface MLWorkerElectron { computeFaceEmbeddings: (input: Float32Array) => Promise; computeCLIPImageEmbedding: (input: Float32Array) => Promise; } + +/** + * Callbacks invoked by the worker at various points in the indexing pipeline to + * notify the main thread of events it might be interested in. + */ +export interface MLWorkerDelegate { + /** + * Called whenever a file is processed during indexing. + * + * It is called both when the indexing was successful or failed. + */ + workerDidProcessFile: () => void; +} diff --git a/web/packages/new/photos/services/ml/worker.ts b/web/packages/new/photos/services/ml/worker.ts index e71d56a85d..22013d0811 100644 --- a/web/packages/new/photos/services/ml/worker.ts +++ b/web/packages/new/photos/services/ml/worker.ts @@ -22,7 +22,7 @@ import { } from "./db"; import { pullFaceEmbeddings, putCLIPIndex, putFaceIndex } from "./embedding"; import { indexFaces, type FaceIndex } from "./face"; -import type { MLWorkerElectron } from "./worker-electron"; +import type { MLWorkerDelegate, MLWorkerElectron } from "./worker-types"; const idleDurationStart = 5; /* 5 seconds */ const idleDurationMax = 16 * 60; /* 16 minutes */ @@ -56,6 +56,7 @@ interface IndexableItem { */ export class MLWorker { private electron: MLWorkerElectron | undefined; + private delegate: MLWorkerDelegate | undefined; private userAgent: string | undefined; private state: "idle" | "pull" | "indexing" = "idle"; private shouldPull = false; @@ -73,9 +74,13 @@ export class MLWorker { * @param electron The {@link MLWorkerElectron} that allows the worker to * use the functionality provided by our Node.js layer when running in the * context of our desktop app + * + * @param delegate The {@link MLWorkerDelegate} the worker can use to inform + * the main thread of interesting events. */ - async init(electron: MLWorkerElectron) { + async init(electron: MLWorkerElectron, delegate?: MLWorkerDelegate) { this.electron = electron; + this.delegate = delegate; // Set the user agent that'll be set in the generated embeddings. this.userAgent = `${clientPackageName}/${await electron.appVersion()}`; // Initialize the downloadManager running in the web worker with the @@ -202,6 +207,7 @@ export class MLWorker { items, ensure(this.electron), ensure(this.userAgent), + this.delegate, ); if (allSuccess) { // Everything is running smoothly. Reset the idle duration. @@ -276,6 +282,7 @@ const indexNextBatch = async ( items: IndexableItem[], electron: MLWorkerElectron, userAgent: string, + delegate: MLWorkerDelegate | undefined, ) => { // Don't try to index if we wouldn't be able to upload them anyway. The // liveQ has already been drained, but that's fine, it'll be rare that we @@ -293,6 +300,7 @@ const indexNextBatch = async ( for (const { enteFile, uploadItem } of items) { try { await index(enteFile, uploadItem, electron, userAgent); + delegate?.workerDidProcessFile(); // Possibly unnecessary, but let us drain the microtask queue. await wait(0); } catch { diff --git a/web/packages/new/photos/services/remote-store.ts b/web/packages/new/photos/services/remote-store.ts new file mode 100644 index 0000000000..ac4d6941a6 --- /dev/null +++ b/web/packages/new/photos/services/remote-store.ts @@ -0,0 +1,44 @@ +import { authenticatedRequestHeaders, ensureOk } from "@/next/http"; +import { apiURL } from "@/next/origins"; +import { z } from "zod"; + +/** + * Fetch the value for the given {@link key} from remote store. + * + * If the key is not present in the remote store, return `undefined`. + */ +export const getRemoteValue = async (key: string) => { + const url = await apiURL("/remote-store"); + const params = new URLSearchParams({ key }); + const res = await fetch(`${url}?${params.toString()}`, { + headers: await authenticatedRequestHeaders(), + }); + ensureOk(res); + return GetRemoteStoreResponse.parse(await res.json())?.value; +}; + +const GetRemoteStoreResponse = z.object({ value: z.string() }).nullable(); + +/** + * Convenience wrapper over {@link getRemoteValue} that returns booleans. + */ +export const getRemoteFlag = async (key: string) => + (await getRemoteValue(key)) == "true"; + +/** + * Update or insert {@link value} for the given {@link key} into remote store. + */ +export const updateRemoteValue = async (key: string, value: string) => + ensureOk( + await fetch(await apiURL("/remote-store/update"), { + method: "POST", + headers: await authenticatedRequestHeaders(), + body: JSON.stringify({ key, value }), + }), + ); + +/** + * Convenience wrapper over {@link updateRemoteValue} that sets booleans. + */ +export const updateRemoteFlag = (key: string, value: boolean) => + updateRemoteValue(key, JSON.stringify(value)); diff --git a/web/packages/new/photos/types/context.ts b/web/packages/new/photos/types/context.ts new file mode 100644 index 0000000000..6076bc22a7 --- /dev/null +++ b/web/packages/new/photos/types/context.ts @@ -0,0 +1,18 @@ +import type { DialogBoxAttributesV2 } from "@ente/shared/components/DialogBoxV2/types"; + +/** + * A subset of the AppContext type used by the photos app. + * + * [Note: Migrating components that need the app context] + * + * This only exists to make it easier to migrate code into the @/new package. + * Once we move this code back (after TypeScript strict mode migration is done), + * then the code that uses this can start directly using the actual app context + * instead of needing to explicitly pass a prop of this type. + * */ +export interface NewAppContextPhotos { + startLoading: () => void; + finishLoading: () => void; + setDialogBoxAttributesV2: (attrs: DialogBoxAttributesV2) => void; + somethingWentWrong: () => void; +} diff --git a/web/packages/new/photos/utils/native-stream.ts b/web/packages/new/photos/utils/native-stream.ts index 70b052b4c1..0475f070db 100644 --- a/web/packages/new/photos/utils/native-stream.ts +++ b/web/packages/new/photos/utils/native-stream.ts @@ -7,7 +7,7 @@ */ import type { Electron, ZipItem } from "@/next/types/ipc"; -import type { MLWorkerElectron } from "../services/ml/worker-electron"; +import type { MLWorkerElectron } from "../services/ml/worker-types"; /** * Stream the given file or zip entry from the user's local file system. diff --git a/web/packages/new/photos/utils/web.ts b/web/packages/new/photos/utils/web.ts index 18f72bd61e..7236cab798 100644 --- a/web/packages/new/photos/utils/web.ts +++ b/web/packages/new/photos/utils/web.ts @@ -1,3 +1,16 @@ +/** + * Open the given {@link url} in a new browser tab. + * + * @param url The URL to open. + */ +export const openURL = (url: string) => { + const a = document.createElement("a"); + a.href = url; + a.target = "_blank"; + a.rel = "noopener"; + a.click(); +}; + /** * Open the system configured email client, initiating a new email to the given * {@link email} address. @@ -5,6 +18,6 @@ export const initiateEmail = (email: string) => { const a = document.createElement("a"); a.href = "mailto:" + email; - a.rel = "noreferrer noopener"; + a.rel = "noopener"; a.click(); }; diff --git a/web/apps/photos/src/components/EnteDrawer.tsx b/web/packages/new/shared/components/EnteDrawer.tsx similarity index 100% rename from web/apps/photos/src/components/EnteDrawer.tsx rename to web/packages/new/shared/components/EnteDrawer.tsx diff --git a/web/packages/new/shared/components/Menu.tsx b/web/packages/new/shared/components/Menu.tsx new file mode 100644 index 0000000000..00381aea67 --- /dev/null +++ b/web/packages/new/shared/components/Menu.tsx @@ -0,0 +1,70 @@ +import { VerticallyCenteredFlex } from "@ente/shared/components/Container"; +import { Divider, styled, Typography } from "@mui/material"; +import React from "react"; + +interface MenuSectionTitleProps { + title: string; + icon?: JSX.Element; +} + +export const MenuSectionTitle: React.FC = ({ + title, + icon, +}) => { + return ( + svg": { + fontSize: "17px", + color: (theme) => theme.colors.stroke.muted, + }, + }} + > + {icon && icon} + + {title} + + + ); +}; + +interface MenuItemDividerProps { + hasIcon?: boolean; +} + +export const MenuItemDivider: React.FC = ({ + hasIcon, +}) => { + return ( + + ); +}; + +export const MenuItemGroup = styled("div")( + ({ theme }) => ` + & > .MuiMenuItem-root{ + border-radius: 8px; + background-color: transparent; + } + & > .MuiMenuItem-root:not(:last-of-type) { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + & > .MuiMenuItem-root:not(:first-of-type) { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + background-color: ${theme.colors.fill.faint}; + border-radius: 8px; +`, +); diff --git a/web/packages/shared/components/Titlebar.tsx b/web/packages/new/shared/components/Titlebar.tsx similarity index 93% rename from web/packages/shared/components/Titlebar.tsx rename to web/packages/new/shared/components/Titlebar.tsx index ed9089f4c2..e46fb92ce0 100644 --- a/web/packages/shared/components/Titlebar.tsx +++ b/web/packages/new/shared/components/Titlebar.tsx @@ -2,8 +2,9 @@ import { FlexWrapper } from "@ente/shared/components/Container"; import ArrowBack from "@mui/icons-material/ArrowBack"; import Close from "@mui/icons-material/Close"; import { Box, IconButton, Typography } from "@mui/material"; +import React from "react"; -interface Iprops { +interface TitlebarProps { title: string; caption?: string; onClose: () => void; @@ -12,14 +13,14 @@ interface Iprops { actionButton?: JSX.Element; } -export default function Titlebar({ +export const Titlebar: React.FC = ({ title, caption, onClose, backIsClose, actionButton, onRootClose, -}: Iprops): JSX.Element { +}) => { return ( <> ); -} +}; diff --git a/web/packages/new/common/crypto/ente.ts b/web/packages/new/shared/crypto/ente.ts similarity index 100% rename from web/packages/new/common/crypto/ente.ts rename to web/packages/new/shared/crypto/ente.ts diff --git a/web/packages/next/i18n.ts b/web/packages/next/i18n.ts index cec28add94..9cd5293832 100644 --- a/web/packages/next/i18n.ts +++ b/web/packages/next/i18n.ts @@ -136,7 +136,7 @@ const savedLocaleStringMigratingIfNeeded = (): SupportedLocale | undefined => { // This migration is dated Feb 2024. And it can be removed after a few // months, because by then either customers would've opened the app and // their setting migrated to the new format, or the browser would've cleared - // the older local storage entry anyway. + // the older local storage entry anyway (tag: Migration). if (!ls) { // Nothing found diff --git a/web/packages/shared/components/DialogBox/types.ts b/web/packages/shared/components/DialogBox/types.ts index 08b52fe4c3..944cd79017 100644 --- a/web/packages/shared/components/DialogBox/types.ts +++ b/web/packages/shared/components/DialogBox/types.ts @@ -1,4 +1,4 @@ -import { ButtonProps } from "@mui/material"; +import type { ButtonProps } from "@mui/material"; export interface DialogBoxAttributes { icon?: React.ReactNode; diff --git a/web/packages/shared/components/EnteDrawer.tsx b/web/packages/shared/components/EnteDrawer.tsx deleted file mode 100644 index e6fc35bb15..0000000000 --- a/web/packages/shared/components/EnteDrawer.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Drawer, styled } from "@mui/material"; - -export const EnteDrawer = styled(Drawer)(({ theme }) => ({ - "& .MuiPaper-root": { - maxWidth: "375px", - width: "100%", - scrollbarWidth: "thin", - padding: theme.spacing(1), - }, -})); diff --git a/web/packages/shared/components/Menu/EnteMenuItem.tsx b/web/packages/shared/components/Menu/EnteMenuItem.tsx index 042348482b..ba52e78701 100644 --- a/web/packages/shared/components/Menu/EnteMenuItem.tsx +++ b/web/packages/shared/components/Menu/EnteMenuItem.tsx @@ -72,6 +72,7 @@ export function EnteMenuItem({ diff --git a/web/packages/shared/components/Menu/MenuItemDivider.tsx b/web/packages/shared/components/Menu/MenuItemDivider.tsx deleted file mode 100644 index da3b309a2b..0000000000 --- a/web/packages/shared/components/Menu/MenuItemDivider.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Divider } from "@mui/material"; -interface Iprops { - hasIcon?: boolean; -} -export default function MenuItemDivider({ hasIcon = false }: Iprops) { - return ( - - ); -} diff --git a/web/packages/shared/components/Menu/MenuItemGroup.tsx b/web/packages/shared/components/Menu/MenuItemGroup.tsx deleted file mode 100644 index 0b80262b5f..0000000000 --- a/web/packages/shared/components/Menu/MenuItemGroup.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { styled } from "@mui/material"; - -export const MenuItemGroup = styled("div")( - ({ theme }) => ` - & > .MuiMenuItem-root{ - border-radius: 8px; - background-color: transparent; - } - & > .MuiMenuItem-root:not(:last-of-type) { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - & > .MuiMenuItem-root:not(:first-of-type) { - border-top-left-radius: 0; - border-top-right-radius: 0; - } - background-color: ${theme.colors.fill.faint}; - border-radius: 8px; -`, -); diff --git a/web/packages/shared/components/Menu/MenuSectionTitle.tsx b/web/packages/shared/components/Menu/MenuSectionTitle.tsx deleted file mode 100644 index 5c07b8d92b..0000000000 --- a/web/packages/shared/components/Menu/MenuSectionTitle.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { VerticallyCenteredFlex } from "@ente/shared/components/Container"; -import { Typography } from "@mui/material"; - -interface Iprops { - title: string; - icon?: JSX.Element; -} - -export default function MenuSectionTitle({ title, icon }: Iprops) { - return ( - svg": { - fontSize: "17px", - color: (theme) => theme.colors.stroke.muted, - }, - }} - > - {icon && icon} - - {title} - - - ); -}