diff --git a/_locales/az/messages.json b/_locales/az/messages.json index 69ae0076b..61913a6c0 100644 --- a/_locales/az/messages.json +++ b/_locales/az/messages.json @@ -196,10 +196,11 @@ "cameraGrantAccessDescription": "Session foto və video çəkmək və ya QR kodlarını skan etmək üçün kameraya müraciət etməlidir.", "cameraGrantAccessQr": "Session QR kodlarını skan etmək üçün kameraya müraciət etməlidir", "cancel": "İmtina", - "cancelPlan": "Planı ləğv et", "change": "Dəyişdir", "changePasswordFail": "Parol dəyişdirmə uğursuz oldu", "changePasswordModalDescription": "Session üçün parolunuzu dəyişdirin. Daxili olaraq saxlanılmış verilər, yeni parolunuzla təkrar şifrələnəcək.", + "checkingProStatus": "Pro statusu yoxlanılır", + "checkingProStatusUpgradeDescription": "Pro statusunuz yoxlanılır. Bu yoxlama tamamlandıqdan sonra Pro ya yüksəldə biləcəksiniz.", "clear": "Təmizlə", "clearAll": "Hamısını təmizlə", "clearDataAll": "Bütün veriləri təmizlə", @@ -301,7 +302,6 @@ "create": "Yarat", "creatingCall": "Zəng yaradılır", "currentPassword": "Hazırkı parol", - "currentPlan": "Hazırkı plan", "cut": "Kəs", "darkMode": "Qaranlıq rejim", "databaseErrorClearDataWarning": "Bu cihazdan bütün mesajları, qoşmaları və hesab məlumatlarını silmək və yeni hesab yaratmaq istədiyinizə əminsinizmi?", @@ -551,7 +551,9 @@ "inviteFailedDescription": "{count, plural, one [Dəvət göndərilə bilmədi. Təkrar cəhd etmək istəyirsiniz?] other [Dəvətlər göndərilə bilmədi. Təkrar cəhd etmək istəyirsiniz?]}", "join": "Qoşul", "later": "Daha sonra", + "launchOnStartDescriptionDesktop": "Kompüteriniz açıldığı zaman Session-u avtomatik başlat.", "launchOnStartDesktop": "Açılışda başlat", + "launchOnStartupDisabledDesktop": "Bu ayar, Linux-dakı sisteminiz tərəfindən idarə olunur. Avtomatik açılışı fəallaşdırmaq üçün sistem ayarlarında Session tətbiqini açılış tətbiqlərinizə əlavə edin.", "learnMore": "Daha ətraflı", "leave": "Tərk et", "leaving": "Tərk edilir...", @@ -720,7 +722,6 @@ "okay": "Yaxşı", "on": "Açıq", "onDevice": "{device_type} cihazınızda", - "onDeviceDescription": "Başda qeydiyyatdan keçdiyiniz {platform_account} hesabına giriş etdiyiniz {device_type} cihazından bu Session hesabını açın. Sonra planınızı Session Pro ayarları vasitəsilə dəyişdirin.", "onboardingAccountCreate": "Hesab yarat", "onboardingAccountCreated": "Hesab yaradıldı", "onboardingAccountExists": "Hesabım var", @@ -745,7 +746,6 @@ "onsErrorNotRecognized": "Bu ONS-i tanıya bilmədik. Lütfən, yoxlayıb yenidən sınayın.", "onsErrorUnableToSearch": "Bu ONS-i axtara bilmədik. Lütfən, daha sonra yenidən sınayın.", "open": "Aç", - "openStoreWebsite": "{platform_store} veb saytını aç", "openSurvey": "Anketi aç", "other": "Digər", "oxenFoundation": "Oxen Foundation", @@ -826,7 +826,6 @@ "pro": "Pro", "proActivated": "Aktivləşdirildi", "proAllSet": "Hər şey hazırdır!", - "proAllSetDescription": "Session Pro planınız güncəlləndi! Hazırkı Pro planınız avtomatik olaraq {date} tarixində yeniləndiyi zaman ödəniş haqqı alınacaq.", "proAlreadyPurchased": "Artıq yüksəltdiniz", "proAnimatedDisplayPicture": "Getdik və ekran şəkliniz üçün GIF-lər və animasiyalı WebP təsvirləri yükləyin!", "proAnimatedDisplayPictureCallToActionDescription": "Session Pro ilə animasiyalı ekran şəkillərini endirin və premium özəlliklərin kilidini açın", @@ -841,30 +840,27 @@ "proBadges": "Nişanlar", "proBadgesDescription": "Ekran adınızın yanında eksklüziv bir nişanla Session tətbiqini dəstəklədiyinizi göstərin.", "proBadgesSent": "{count, plural, one [{total} Pro nişan göndərildi] other [{total} Pro nişan göndərildi]}", + "proBetaFeatures": "Pro Beta Xüsusiyyətləri", "proBilledAnnually": "{price} - illik haqq", "proBilledMonthly": "{price} - aylıq haqq", "proBilledQuarterly": "{price} - rüblük haqq", "proCallToActionLongerMessages": "Daha uzun mesajlar göndərmək istəyirsiniz? Session Pro ilə daha çox mətn göndərin və premium özəlliklərin kilidini açın", "proCallToActionPinnedConversations": "Daha çoxunu sancmaq istəyirsiniz? Session Pro ilə söhbətlərinizi təşkil edin və premium özəlliklərin kilidini açın", "proCallToActionPinnedConversationsMoreThan": "5-dən çoxunu sancmaq istəyirsiniz? Session Pro ilə söhbətlərinizi təşkil edin və premium özəlliklərin kilidini açın", - "proDiscountTooltip": "Hazırkı planınızda artıq tam Session Pro qiymətinin {percent}% endirimi mövcuddur.", + "proErrorRefreshingStatus": "Pro statusunu təzələmə xətası", "proExpired": "Müddəti bitib", - "proExpiredDescription": "Təəssüf ki, Pro planınızın müddəti bitib. Session Pro tətbiqinin eksklüziv imtiyazlarına və özəlliklərinə erişimi davam etdirmək üçün yeniləyin.", "proExpiringSoon": "Tezliklə bitir", - "proExpiringSoonDescription": "Pro planınızın müddəti {time} vaxtında bitir. Session Pro tətbiqinin eksklüziv imtiyazlarına və özəlliklərinə erişimi davam etdirmək üçün planınızı güncəlləyin.", "proExpiringTime": "Pro, {time} vaxtında başa çatır", "proFaq": "Pro TVS", - "proFaqDescription": "Session TVS-da tez-tez verilən suallara cavab tapın.", + "proFaqDescription": "Session Pro TVS-da tez-tez verilən suallara cavab tapın.", "proFeatureListAnimatedDisplayPicture": "GIF və WebP ekran şəkilləri yüklə", "proFeatureListLargerGroups": "300 üzvə qədər daha da böyük qrup söhbətləri", "proFeatureListLoadsMore": "Üstəgəl, daha çox eksklüziv özəlliklər", "proFeatureListLongerMessages": "10,000 xarakterə qədər mesajlar", "proFeatureListPinnedConversations": "Limitsiz danışığı sancın", - "proFeatures": "Pro özəllikləri", "proGroupActivated": "Qrup aktivləşdirildi", "proGroupActivatedDescription": "Bu qurun tutumu artıb! Qrup admininin sayəsində artıq 300 nəfərə qədər üzvü dəstəkləyə bilər", "proGroupsUpgraded": "{count, plural, one [{total} qrup yüksəldildi] other [{total} qrup yüksəldildi]}", - "proImportantDescription": "Geri ödəniş tələbi qətidir. Əgər təsdiqlənsə, Pro planınız dərhal ləğv ediləcək və bütün Pro özəlliklərinə erişimi itirəcəksiniz.", "proIncreasedAttachmentSizeFeature": "Artırılmış qoşma ölçüsü", "proIncreasedMessageLengthFeature": "Artırılmış mesaj uzunluğu", "proLargerGroups": "Daha böyük qruplar", @@ -875,46 +871,30 @@ "proMessageInfoFeatures": "Bu mesajda aşağıdakı Session Pro özəllikləri istifadə olunub:", "proPercentOff": "{percent}% endirim", "proPinnedConversations": "{count, plural, one [{total} sancılmış danışıq] other [{total} sancılmış danışıq]}", - "proPlanActivatedAuto": "Session Pro planınız aktivdir!

Planınız avtomatik olaraq {date} tarixində başqa bir {current_plan} üçün yenilənəcək. Planınıza edilən güncəlləmələr növbəti Pro yenilənməsi zamanı qüvvəyə minəcək.", - "proPlanActivatedAutoShort": "Session Pro planınız aktivdir!

Planınız avtomatik olaraq {date} tarixində başqa bir {current_plan} üçün yenilənəcək.", - "proPlanActivatedNotAuto": "Session Pro planınızın müddəti {date} tarixində bitir.

Eksklüziv Pro özəlliklərinə kəsintisiz erişimi təmin etmək üçün planınızı indi güncəlləyin.", - "proPlanExpireDate": "Session Pro planınızın müddəti {date} tarixində bitir.", - "proPlanNotFound": "Pro planı tapılmadı", - "proPlanNotFoundDescription": "Hesabınız üçün heç bir aktiv plan tapılmadı. Bunun bir səhv olduğunu düşünürsünüzsə, lütfən kömək üçün Session dəstəyi ilə əlaqə saxlayın.", - "proPlanPlatformRefund": "Başda {platform_store} Mağazası üzərindən Session Pro üçün qeydiyyatdan keçdiyinizə görə, geri ödəmə tələbini göndərmək üçün eyni {platform_account} hesabını istifadə etməlisiniz.", - "proPlanPlatformRefundLong": "Başda {platform_store} Mağazası üzərindən Session Pro üçün qeydiyyatdan keçdiyinizə görə, geri qaytarma tələbiniz Session Dəstək komandası tərəfindən icra olunacaq.

Aşağıdakı düyməyə basaraq və geri ödəniş formunu dolduraraq geri ödəmə tələbinizi göndərin.

Session Dəstək komandası, geri ödəmə tələblərini adətən 24-72 saat ərzində emal edir, yüksək tələb həcminə görə bu proses daha uzun çəkə bilər.", - "proPlanRecover": "Pro planını geri qaytar", - "proPlanRenew": "Pro planını yenilə", - "proPlanRenewDesktop": "Hazırda, Pro planları, yalnızca {platform_store} və {platform_store} Mağazaları vasitəsilə satın alına və yenilənə bilər. Session Masaüstü istifadə etdiyinizə görə planınızı burada yeniləyə bilməzsiniz.

Session Pro gəlişdiriciləri, istifadəçilərin Pro planlarını {platform_store} və {platform_store} Mağazalarından kənarda almağına imkan verəcək alternativ ödəniş variantları üzərində ciddi şəkildə çalışırlar. Pro Yol Xəritəsi", - "proPlanRenewDesktopLinked": "{platform_store} və ya {platform_store} Mağazaları vasitəsilə planınızı Session quraşdırılmış və əlaqələndirilmiş cihazda Session Pro ayarlarında yeniləyin.", - "proPlanRenewDesktopStore": "Pro üçün qeydiyyatdan keçdiyiniz {platform_account} hesabınızla {platform_store} veb saytında planınızı yeniləyin.", - "proPlanRenewSupport": "Session Pro planınız yeniləndi! Session Network dəstək verdiyiniz üçün təşəkkürlər.", - "proPlanRestored": "Pro planı bərpa edildi", - "proPlanRestoredDescription": "Session Pro üçün yararlı bir plan aşkarlandı və Pro statusunuz bərpa edildi!", - "proPlanSignUp": "Başda {platform_store} Mağazası üzərindən Session Pro üçün qeydiyyatdan keçdiyinizə görə planınızı həmin {platform_account} vasitəsilə güncəlləməlisiniz.", "proPriceOneMonth": "1 ay - {monthly_price}/ay", "proPriceThreeMonths": "3 ay - {monthly_price}/ay", "proPriceTwelveMonths": "12 ay - {monthly_price}/ay", "proRefundDescription": "Getməyinizə məyus olduq. Geri ödəmə tələb etməzdən əvvəl bilməli olduğunuz şeylər.", - "proRefundNextSteps": "{platform_account} hazırda geri ödəniş tələbinizi emal edir. Bu, adətən 24-48 saat çəkir. Onların qərarından asılı olaraq, Session tətbiqində Pro statusunuzun dəyişdiyini görə bilərsiniz.", "proRefundRequestSessionSupport": "Geri qaytarma tələbiniz Session Dəstək komandası tərəfindən icra olunacaq.

Aşağıdakı düyməyə basaraq və geri ödəniş formunu dolduraraq geri ödəniş tələbinizi göndərin.

Session Dəstək komandası, geri ödəniş tələblərini adətən 24-72 saat ərzində emal edir, yüksək tələb həcminə görə bu proses daha uzun çəkə bilər.", - "proRefundRequestStorePolicies": "Geri ödəniş tələbiniz yalnız {platform_account} veb saytında {platform_account} hesabı üzərindən icra olunacaq.

{platform_account} geri ödəniş siyasətlərinə əsasən, Session gəlişdiriciləri, geri ödəniş tələblərinin nəticəsinə təsir edə bilməz. Bu, tələbin qəbul olunub-olunmaması ilə yanaşı, tam və ya qismən geri ödənişin verilib-verilməməsini də əhatə edir.", - "proRefundSupport": "Geri ödəmə tələbinizlə bağlı daha çox güncəlləmə üçün lütfən {platform_account} ilə əlaqə saxlayın. {platform_account} geri ödəniş siyasətlərinə əsasən, Session gəlişdiriciləri, geri ödəniş tələblərinin nəticəsinə təsir edə bilməz.

{platform_store} Geri ödəmə dəstəyi", "proRefunding": "Pro geri ödəməsi", - "proRefundingDescription": "Session Pro planları üçün geri ödəmələr yalnız {platform_store} Mağazası vasitəsilə {platform_account} tərəfindən həyata keçirilir.

{platform_account} geri ödəniş siyasətlərinə əsasən, Session gəlişdiriciləri, geri ödəniş tələblərinin nəticəsinə təsir edə bilməz. Bu, tələbin qəbul olunub-olunmaması ilə yanaşı, tam və ya qismən geri ödənişin verilib-verilməməsini də əhatə edir.", "proRequestedRefund": "Geri ödəmə tələb edildi", "proSendMore": "Daha çoxunu göndərmək üçün", "proSettings": "Pro ayarları", "proStats": "Pro statistikalarınız", + "proStatsLoading": "Pro statistikaları yüklənir", + "proStatsLoadingDescription": "Pro statistikalarınız yüklənir, lütfən gözləyin.", "proStatsTooltip": "Pro statistikaları, bu cihazdakı istifadəni əks-etdirir və əlaqələndirilmiş cihazlarda fərqli görünə bilər.", - "proSupportDescription": "Pro planınızla bağlı kömək lazımdır? Dəstək komandamıza müraciət edin.", + "proStatusError": "Pro status xətası", + "proStatusInfoInaccurateNetworkError": "Pro statusunuzu yoxlamaq üçün şəbəkəyə bağlana bilmir. Bu səhifədə nümayiş olan məlumatlar, bağlantı bərpa olunana qədər qeyri-dəqiq ola bilər.

Lütfən şəbəkə bağlantınızı yoxlayıb yenidən sınayın.", + "proStatusLoading": "Pro statusu yüklənir", + "proStatusLoadingDescription": "Pro məlumatlarınız yüklənir. Bu səhifədəki bəzi əməliyyatlar yükləmə tamamlanana qədər əlçatan olmaya bilər.", + "proStatusLoadingSubtitle": "Pro status yüklənir", + "proStatusNetworkErrorDescription": "Pro statusunuzu yoxlamaq üçün şəbəkəyə bağlana bilmir. Bağlantı bərpa olunana qədər Pro ya yüksəldə bilməyəcəksiniz.

Lütfən şəbəkə bağlantınızı yoxlayıb yenidən sınayın.", + "proStatusRefreshNetworkError": "Pro statusunuzu təzələmək üçün şəbəkəyə bağlana bilmir. Bu səhifədəki bəzi əməliyyatlar, bağlantı bərpa olunana qədər sıradan çıxarılacaq.

Lütfən şəbəkə bağlantınızı yoxlayıb yenidən sınayın.", "proTosPrivacy": "Güncəlləyərək, Session Pro Xidmət Şərtləri {icon} və Məxfilik Siyasəti {icon} ilə razılaşırsınız", "proUnlimitedPins": "Limitsiz sancma", "proUnlimitedPinsDescription": "Limitsiz sancılmış danışıqla bütün söhbətlərinizi təşkil edin.", - "proUpdatePlanDescription": "Hazırda {current_plan} Planı üzərindəsiniz. {selected_plan} Planınana keçmək istədiyinizə əminsiniz?

Güncəlləsəniz, planınız {date} tarixində əlavə {selected_plan} Pro erişimi üçün avtomatik yenilənəcək.", - "proUpdatePlanExpireDescription": "Planınız {date} tarixində bitəcək.

Güncəlləsəniz, planınız {date} tarixində əlavə {selected_plan} Pro erişimi üçün avtomatik olaraq yenilənəcək.", "proUserProfileModalCallToAction": "Session tətbiqindən daha çox faydalanmaq istəyirsiniz? Daha güclü mesajlaşma təcrübəsi üçün Session Pro-ya yüksəldin.", - "processingRefundRequest": "{platform_account} geri ödəmə tələbinizi emal edir", "profile": "Profil", "profileDisplayPicture": "Ekran şəkli", "profileDisplayPictureRemoveError": "Ekran şəklini silmə uğursuz oldu.", @@ -964,7 +944,6 @@ "recoveryPasswordWarningSendDescription": "Bu, sizin geri qaytarma parolunuzdur. Kiməsə göndərsəniz, hesabınıza tam erişə bilər.", "recreateGroup": "Qrupu yenidən yarat", "redo": "Təkrar et", - "refundPlanNonOriginatorApple": "Başda fərqli {platform_account} vasitəsilə Session Pro üçün qeydiyyatdan keçdiyinizə görə planınızı həmin {platform_account} vasitəsilə güncəlləməlisiniz.", "remainingCharactersOverTooltip": "Mesajın uzunluğunu {count} qədər azalt", "remainingCharactersTooltip": "{count, plural, one [{count} xarakter qaldı] other [{count} xarakter qaldı]}", "remove": "Sil", @@ -988,6 +967,8 @@ "screenSecurity": "Ekran güvənliyi", "screenshotNotifications": "Ekran şəkli bildirişi", "screenshotNotificationsDescription": "Birə bir söhbətdə qarşı tərəf ekran şəklini çəkəndə bir bildiriş al.", + "screenshotProtectionDescriptionDesktop": "Bu cihazda çəkilən ekran şəkillərində Session pəncərəsini gizlət.", + "screenshotProtectionDesktop": "Ekran şəkli qoruması", "screenshotTaken": "{name} ekran şəklini çəkdi.", "search": "Axtar", "searchContacts": "Kontaktları axtar", @@ -1074,6 +1055,7 @@ "unavailable": "Əlçatmazdır", "undo": "Geri al", "unknown": "Bilinmir", + "unsupportedCpu": "Dəstəklənməyən CPU", "updateApp": "Tətbiq güncəlləmələri", "updateCommunityInformation": "İcma məlumatlarını güncəllə", "updateCommunityInformationDescription": "İcma adı və açıqlaması, bütün icma üzvlərinə görünür", @@ -1088,8 +1070,6 @@ "updateGroupInformationEnterShorterDescription": "Zəhmət olmasa, qrupun daha qısa təsvirini daxil edin", "updateNewVersion": "Session üçün yeni versiyası mövcuddur, güncəlləmək üçün toxunun", "updateNewVersionDescription": "Yeni Session versiyası ({version}) mövcuddur.", - "updatePlan": "Planı güncəllə", - "updatePlanTwo": "Planınızı güncəlləməyin iki yolu var:", "updateProfileInformation": "Profil məlumatlarını güncəllə", "updateProfileInformationDescription": "Ekran adınız və ekran şəkliniz bütün danışıqlarda görünür.", "updateReleaseNotes": "Buraxılış qeydlərinə get", @@ -1107,8 +1087,6 @@ "urlOpenDescriptionAlternative": "Keçidlər, brauzerinizdə açılacaq.", "usdNameShort": "USD", "useFastMode": "Sürətli rejimi istifadə et", - "viaStoreWebsite": "{platform_store} veb saytı vasitəsilə", - "viaStoreWebsiteDescription": "Qeydiyyatdan keçərkən istifadə etdiyiniz {platform_account} hesabı ilə {platform_store} veb saytı üzərindən planınızı dəyişdirin.", "video": "Video", "videoErrorPlay": "Video oxudula bilmir.", "view": "Göstər", @@ -1120,6 +1098,7 @@ "window": "Pəncərə", "yes": "Bəli", "you": "Siz", + "yourCpuIsUnsupportedSSE42": "CPU-nuz Linux x64 əməliyyat sistemlərində Session-un təsvirləri emal etməsi üçün tələb olunan SSE 4.2 təlimatlarını dəstəkləmir. Lütfən uyumlu bir CPU-ya keçin və ya fərqli bir əməliyyat sistemi istifadə edin.", "yourRecoveryPassword": "Geri qaytarma parolunuz", "zoomFactor": "Böyütmə amili", "zoomFactorDescription": "Mətnin və vizual elementlərin ölçüsünü ayarla." diff --git a/_locales/cs/messages.json b/_locales/cs/messages.json index 51793cf0c..3d69d5b9c 100644 --- a/_locales/cs/messages.json +++ b/_locales/cs/messages.json @@ -196,10 +196,11 @@ "cameraGrantAccessDescription": "Session potřebuje přístup k fotoaparátu pro pořizování fotografií a videí nebo skenování QR kódů.", "cameraGrantAccessQr": "Session potřebuje přístup k fotoaparátu ke skenování QR kódů", "cancel": "Zrušit", - "cancelPlan": "Zrušit tarif", "change": "Změnit", "changePasswordFail": "Změna hesla selhala", "changePasswordModalDescription": "Změňte své heslo pro Session. Lokálně uložená data budou znovu zašifrována pomocí vašeho nového hesla.", + "checkingProStatus": "Kontrola stavu Pro", + "checkingProStatusUpgradeDescription": "Kontroluje se váš stav Pro. Jakmile kontrola skončí, budete moci navýšit na Pro.", "clear": "Smazat", "clearAll": "Smazat vše", "clearDataAll": "Smazat všechna data", @@ -301,7 +302,6 @@ "create": "Vytvořit", "creatingCall": "Vytváření hovoru", "currentPassword": "Aktuální heslo", - "currentPlan": "Současný tarif", "cut": "Vyjmout", "darkMode": "Tmavý režim", "databaseErrorClearDataWarning": "Opravdu chcete smazat všechny zprávy, přílohy a data účtu z tohoto zařízení a vytvořit nový účet?", @@ -722,7 +722,6 @@ "okay": "OK", "on": "Zap.", "onDevice": "Na vašem zařízení {device_type}", - "onDeviceDescription": "Otevřete tento účet Session na zařízení {device_type}, které je přihlášeno do účtu {platform_account}, pomocí kterého jste se původně zaregistrovali. Poté změňte svůj tarif v nastavení Session Pro.", "onboardingAccountCreate": "Vytvořit účet", "onboardingAccountCreated": "Účet vytvořen", "onboardingAccountExists": "Mám účet", @@ -747,7 +746,6 @@ "onsErrorNotRecognized": "Nepodařilo se rozpoznat tento ONS. Zkontrolujte ho a zkuste to znovu.", "onsErrorUnableToSearch": "Nepodařilo se vyhledat tento ONS. Zkuste to prosím později.", "open": "Otevřít", - "openStoreWebsite": "Otevřít webovou stránku {platform_store}", "openSurvey": "Otevřít dotazník", "other": "Ostatní", "oxenFoundation": "Oxen Foundation", @@ -828,7 +826,6 @@ "pro": "Pro", "proActivated": "Aktivováno", "proAllSet": "Vše je nastaveno!", - "proAllSetDescription": "Váš tarif Session Pro byl aktualizován! Účtování proběhne při automatickém obnovení vašeho aktuálního tarifu Pro dne {date}.", "proAlreadyPurchased": "Už máte", "proAnimatedDisplayPicture": "Jako váš zobrazovaný profilový obrázek nastavte animovaný GIF nebo WebP!", "proAnimatedDisplayPictureCallToActionDescription": "Získejte možnost nahrát animovaný zobrazovaný obrázek profilu a další prémiové funkce se Session Pro", @@ -837,86 +834,68 @@ "proAnimatedDisplayPictures": "Animované zobrazované obrázky", "proAnimatedDisplayPicturesDescription": "Nastavte si animované obrázky GIF a WebP jako svůj zobrazovaný profilový obrázek.", "proAnimatedDisplayPicturesNonProModalDescription": "Nahrajte GIFy se", - "proAutoRenewTime": "Pro se automaticky obnoví za {time}", "proBadge": "Odznak Pro", "proBadgeVisible": "Zobrazit odznak Session Pro ostatním uživatelům", "proBadges": "Odznaky", "proBadgesDescription": "Vyjádřete svou podporu Session pomocí exkluzivního odznaku vedle svého zobrazovaného jména.", "proBadgesSent": "{count, plural, one [{total} Pro odznak odeslán] few [{total} Pro odznaky odeslány] many [{total} Pro odznaků odesláno] other [{total} Pro odznaků odesláno]}", + "proBetaFeatures": "Funkce beta verze Pro", "proBilledAnnually": "{price} účtováno ročně", "proBilledMonthly": "{price} účtováno měsíčně", "proBilledQuarterly": "{price} účtováno čtvrtletně", "proCallToActionLongerMessages": "Chcete posílat delší zprávy? Posílejte více textu odemknutím prémiových funkcí Session Pro", "proCallToActionPinnedConversations": "Chcete více připnutí? Organizujte své chaty a odemkněte prémiové funkce pomocí Session Pro", "proCallToActionPinnedConversationsMoreThan": "Chcete více než 5 připnutí? Organizujte své chaty a odemkněte prémiové funkce pomocí Session Pro", - "proDiscountTooltip": "Váš aktuální tarif je již zlevněn o {percent} % z plné ceny Session Pro.", + "proCancellation": "Zrušit", + "proErrorRefreshingStatus": "Chyba obnovování stavu Pro", "proExpired": "Platnost vypršela", - "proExpiredDescription": "Bohužel, váš tarif Pro vypršel. Obnovte jej, abyste nadále měli přístup k exkluzivním výhodám a funkcím Session Pro.", "proExpiringSoon": "Brzy vyprší", - "proExpiringSoonDescription": "Váš tarif Pro vyprší za {time}. Aktualizujte si tarif, abyste i nadále měli přístup k exkluzivním výhodám a funkcím Session Pro.", "proExpiringTime": "Pro vyprší za {time}", "proFaq": "Pro FAQ", - "proFaqDescription": "Najděte odpovědi na časté dotazy v nápovědě Session.", + "proFaqDescription": "Najděte odpovědi na časté dotazy v nápovědě Session Pro.", "proFeatureListAnimatedDisplayPicture": "Nahrajte GIF a WebP jako zobrazovaný obrázek", "proFeatureListLargerGroups": "Větší soukromé skupiny až 300 členů", "proFeatureListLoadsMore": "A další exkluzivních funkce", "proFeatureListLongerMessages": "Zprávy až do 10000 znaků", "proFeatureListPinnedConversations": "Připněte neomezený počet konverzací", - "proFeatures": "Funkce Pro", "proGroupActivated": "Skupina aktivována", "proGroupActivatedDescription": "Tato skupina má navýšenou kapacitu! Může podporovat až 300 členů, protože správce skupiny má", "proGroupsUpgraded": "{count, plural, one [{total} skupina navýšena] few [{total} skupiny navýšeny] many [{total} skupin navýšeno] other [{total} skupin navýšeno]}", - "proImportantDescription": "Žádost o vrácení peněz je konečné. Pokud bude schváleno, váš tarif Pro bude ihned zrušen a ztratíte přístup ke všem funkcím Pro.", "proIncreasedAttachmentSizeFeature": "Navýšená velikost přílohy", "proIncreasedMessageLengthFeature": "Navýšená délka zprávy", "proLargerGroups": "Větší skupiny", "proLargerGroupsDescription": "Skupiny, ve kterých jste správcem, jsou automaticky navýšeny na kapacitu až 300 členů.", + "proLargerGroupsTooltip": "Větší skupinové chaty (až pro 300 členů) brzy budou k dispozici pro všechny uživatele Pro Beta!", "proLongerMessages": "Delší zprávy", "proLongerMessagesDescription": "Ve všech konverzacích můžete posílat zprávy až o délce 10 000 znaků.", "proLongerMessagesSent": "{count, plural, one [{total} delší zpráva odeslána] few [{total} delší zprávy odeslány] many [{total} delších zpráv odesláno] other [{total} delších zpráv odesláno]}", "proMessageInfoFeatures": "V této zprávě byly použity následující funkce Session Pro:", "proPercentOff": "Sleva {percent} %", "proPinnedConversations": "{count, plural, one [{total} připnutá konverzace] few [{total} připnuté konverzace] many [{total} připnutých konverzací] other [{total} připnutých konverzací]}", - "proPlanActivatedAuto": "Váš tarif Session Pro je aktivní!

Váš tarif se automaticky obnoví na další {current_plan} dne {date}. Změny tarifu se projeví při příštím obnovení Pro.", - "proPlanActivatedAutoShort": "Váš tarif Session Pro je aktivní!

Tarif se automaticky obnoví na další {current_plan} dne {date}.", - "proPlanActivatedNotAuto": "Váš tarif Session Pro vyprší dne {date}.

Aktualizujte si tarif nyní, abyste měli i nadále nepřerušený přístup k exkluzivním funkcím Pro.", - "proPlanExpireDate": "Váš tarif Session Pro vyprší dne {date}.", - "proPlanNotFound": "Pro nebyl nalezen", - "proPlanNotFoundDescription": "Pro váš účet nebyl nalezen žádný aktivní tarif. Pokud si myslíte, že se jedná o chybu, kontaktujte podporu Session a požádejte o pomoc.", - "proPlanPlatformRefund": "Protože jste se původně zaregistrovali do Session Pro přes obchod {platform_store}, budete muset pro žádost o vrácení peněz použít stejný účet {platform_account}.", - "proPlanPlatformRefundLong": "Protože jste si původně zakoupili Session Pro přes obchod {platform_store}, váš požadavek na vrácení peněz bude zpracován podporou Session.

Požádejte o vrácení peněz kliknutím na tlačítko níže a vyplněním formuláře žádosti o vrácení peněz.

Ačkoliv se podpora Session snaží zpracovat žádosti o vrácení peněz během 24–72 hodin, může zpracování trvat i déle, pokud dochází k vysokému počtu žádostí.", - "proPlanRecover": "Znovu nabýt tarif Pro", - "proPlanRenew": "Obnovit tarif Pro", - "proPlanRenewDesktop": "V současnosti lze tarify Pro zakoupit a obnovit pouze prostřednictvím obchodů {platform_store} nebo {platform_store}. Protože používáte Session Desktop, nemůžete zde svůj plán obnovit.

Vývojáři Session Pro intenzivně pracují na alternativních platebních možnostech, které by uživatelům umožnily zakoupit tarify Pro mimo obchody {platform_store} a {platform_store}. Plán vývoje Pro", - "proPlanRenewDesktopLinked": "Obnovte svůj tarif v nastavení Session Pro na propojeném zařízení s nainstalovanou aplikací Session prostřednictvím obchodu {platform_store} nebo {platform_store}.", - "proPlanRenewDesktopStore": "Obnovte svůj tarif na webu {platform_store} pomocí účtu {platform_account}, se kterým jste si pořídili Pro.", - "proPlanRenewSupport": "Váš tarif Session Pro byl obnoven! Děkujeme, že podporujete síť Session Network.", - "proPlanRestored": "Pro obnoven", - "proPlanRestoredDescription": "Byl rozpoznán platný tarif Session Pro a váš stav Pro byl obnoven!", - "proPlanSignUp": "Protože jste se původně zaregistrovali do Session Pro přes {platform_store}, je třeba abyste pro aktualizaci vašeho tarifu použili svůj {platform_account}.", "proPriceOneMonth": "1 měsíc – {monthly_price} / měsíc", "proPriceThreeMonths": "3 měsíce – {monthly_price} / měsíc", "proPriceTwelveMonths": "12 měsíců – {monthly_price} / měsíc", "proRefundDescription": "Mrzí nás, že to rušíte. Než požádáte o vrácení peněz, přečtěte si informace, které byste měli vědět.", - "proRefundNextSteps": "{platform_account} nyní zpracovává vaši žádost o vrácení peněz. Obvykle to trvá 24–48 hodin. V závislosti na jejich rozhodnutí se může váš stav Pro v aplikaci Session změnit.", "proRefundRequestSessionSupport": "Váš požadavek na vrácení peněz bude zpracován podporou Session.

Požádejte o vrácení peněz kliknutím na tlačítko níže a vyplněním formuláře žádosti o vrácení peněz.

Ačkoliv se podpora Session snaží zpracovat žádosti o vrácení peněz během 24–72 hodin, může zpracování trvat i déle, v případě že je vyřizováno mnoho žádostí.", - "proRefundRequestStorePolicies": "Vaši žádost o vrácení peněz bude vyřizovat výhradně {platform_account} prostřednictvím webových stránek {platform_account}.

Vzhledem k pravidlům vracení peněz {platform_account} nemají vývojáři Session žádný vliv na výsledek žádostí o vrácení peněz. To zahrnuje i rozhodnutí, zda bude žádost schválena nebo zamítnuta, a také, zda bude vrácena část peněz, nebo všechny peníze.", - "proRefundSupport": "Pro další informace o vaší žádosti o vrácení peněz kontaktujte prosím {platform_account}. Vzhledem k zásadám pro vrácení peněz {platform_account} nemají vývojáři aplikace Session žádnou možnost ovlivnit výsledek žádosti o vrácení.

Podpora vrácení peněz {platform_store}", "proRefunding": "Vracení peněz za Pro", - "proRefundingDescription": "Vrácení peněz za tarify Session Pro je vyřizováno výhradně prostřednictvím {platform_account} v obchodě {platform_store}.

Vzhledem k pravidlům vracení peněz služby {platform_account} nemají vývojáři Session žádný vliv na výsledek žádostí o vrácení peněz. To zahrnuje i rozhodnutí, zda bude žádost schválena nebo zamítnuta, a také, zda bude vrácena část peněz, nebo všechny peníze.", "proRequestedRefund": "Žádost o vrácení peněz", "proSendMore": "Posílejte více se", "proSettings": "Nastavení Pro", "proStats": "Vaše statistiky Pro", + "proStatsLoading": "Načítání statistik Pro", + "proStatsLoadingDescription": "Vaše statistiky Pro se načítají, počkejte prosím.", "proStatsTooltip": "Statistiky Pro ukazují používání na tomto zařízení a mohou se lišit na jiných propojených zařízeních", - "proSupportDescription": "Potřebujete pomoc se svým tarifem Pro? Pošlete žádost týmu podpory.", + "proStatusError": "Chyba stavu Pro", + "proStatusInfoInaccurateNetworkError": "Nelze se připojit k síti, aby bylo možné zkontrolovat váš stav Pro. Informace zobrazené na této stránce mohou být nepřesné, dokud nebude připojení obnoveno.

Zkontrolujte připojení k síti a zkuste to znovu.", + "proStatusLoading": "Stav načítání Pro", + "proStatusLoadingDescription": "Načítají se vaše informace Pro. Některé akce na této stránce nemusí být dostupné, dokud nebude načítání dokončeno.", + "proStatusLoadingSubtitle": "stav načítání Pro", + "proStatusNetworkErrorDescription": "Nelze se připojit k síti, aby bylo možné zkontrolovat váš stav Pro. Dokud nebude obnoveno připojení, nelze provést navýšení na Pro.

Zkontrolujte připojení k síti a zkuste to znovu.", + "proStatusRefreshNetworkError": "Nelze se připojit k síti, aby se obnovil váš stav Pro. Některé akce na této stránce budou deaktivovány, dokud nebude obnoveno připojení.

Zkontrolujte připojení k síti a zkuste to znovu.", "proTosPrivacy": "Aktualizací souhlasíte s Podmínkami služby {icon} a Zásadami ochrany osobních údajů {icon} Session Pro", "proUnlimitedPins": "Neomezený počet připnutí", "proUnlimitedPinsDescription": "Organizujte si komunikaci pomocí neomezeného počtu připnutých konverzací.", - "proUpdatePlanDescription": "V současné době jste na tarifu {current_plan}. Jste si jisti, že chcete přepnout na tarif {selected_plan}?

Po aktualizaci bude váš tarif automaticky obnoven {date} na další {selected_plan} přístupu Pro.", - "proUpdatePlanExpireDescription": "Váš tarif vyprší {date}.

Po aktualizaci se váš tarif automaticky obnoví {date} na další {selected_plan} přístupu Pro.", "proUserProfileModalCallToAction": "Chcete z Session získat více? Navyštee na Session Pro pro výkonnější posílání zpráv.", - "processingRefundRequest": "{platform_account} zpracovává vaši žádost o vrácení peněz", "profile": "Profil", "profileDisplayPicture": "Zobrazovaný obrázek", "profileDisplayPictureRemoveError": "Chyba při odstraňování zobrazovaného obrázku.", @@ -966,13 +945,11 @@ "recoveryPasswordWarningSendDescription": "Toto je vaše heslo pro obnovení. Pokud ho někomu pošlete, bude mít plný přístup k vašemu účtu.", "recreateGroup": "Znovu vytvořit skupinu", "redo": "Znovu", - "refundPlanNonOriginatorApple": "Protože jste se původně zaregistrovali do Session Pro přes jiný {platform_account}, je třeba použít ten {platform_account}, abyste aktualizovali váš tarif.", "remainingCharactersOverTooltip": "Zkraťte délku zprávy o {count}", "remainingCharactersTooltip": "{count, plural, one [Zbývá {count} znak] few [Zbývají {count} znaky] many [Zbývá {count} znaků] other [Zbývá {count} znaků]}", "remove": "Odstranit", "removePasswordFail": "Odebrání hesla selhalo", "removePasswordModalDescription": "Odstraňte své aktuální heslo pro Session. Lokálně uložená data budou znovu zašifrována pomocí náhodně vygenerovaného klíče uloženého ve vašem zařízení.", - "renew": "Obnovit", "reply": "Odpovědět", "requestRefund": "Požádat o vrácení platby", "resend": "Odeslat znovu", @@ -990,6 +967,8 @@ "screenSecurity": "Zabezpečení obrazovky", "screenshotNotifications": "Upozornění na snímek obrazovky", "screenshotNotificationsDescription": "Požadovat upozornění, když kontakt pořídí snímek obrazovky chatu jeden na jednoho.", + "screenshotProtectionDescriptionDesktop": "Skrývat okno Session na snímcích obrazovky pořízených na tomto zařízení.", + "screenshotProtectionDesktop": "Ochrana proti pořizování snímků obrazovky", "screenshotTaken": "{name} pořídil(a) snímek obrazovky.", "search": "Hledat", "searchContacts": "Prohledat kontakty", @@ -1076,6 +1055,7 @@ "unavailable": "Nedostupné", "undo": "Vrátit zpět", "unknown": "Neznámé", + "unsupportedCpu": "Nepodporovaný procesor", "updateApp": "Aktualizace aplikace", "updateCommunityInformation": "Upravit informace o komunitě", "updateCommunityInformationDescription": "Název a popis komunity jsou viditelné pro všechny členy komunity", @@ -1090,8 +1070,6 @@ "updateGroupInformationEnterShorterDescription": "Zadejte prosím kratší popis skupiny", "updateNewVersion": "Je dostupná nová verze Session, klikněte pro aktualizaci", "updateNewVersionDescription": "Je dostupná nová verze ({version}) aplikace Session.", - "updatePlan": "Aktualizovat tarif", - "updatePlanTwo": "Dva způsoby, jak aktualizovat váš tarif:", "updateProfileInformation": "Upravit informace profilu", "updateProfileInformationDescription": "Vaše zobrazované jméno a profilová fotka jsou viditelné ve všech konverzacích.", "updateReleaseNotes": "Přejít na poznámky k vydání", @@ -1100,6 +1078,7 @@ "updated": "Naposledy aktualizováno před {relative_time}", "updates": "Aktualizace", "updating": "Aktualizuji...", + "upgradeSession": "Navýšit Session", "upgradeTo": "Navýšit na", "uploading": "Nahrávání", "urlCopy": "Zkopírovat URL", @@ -1109,8 +1088,6 @@ "urlOpenDescriptionAlternative": "Odkazy se otevřou ve vašem prohlížeči.", "usdNameShort": "USD", "useFastMode": "Použít rychlý režim", - "viaStoreWebsite": "Přes webové stránky {platform_store}", - "viaStoreWebsiteDescription": "Změňte svůj tarif pomocí {platform_account}, se kterým jste se zaregistrovali, prostřednictvím webu {platform_store}.", "video": "Video", "videoErrorPlay": "Nelze přehrát video.", "view": "Zobrazit", @@ -1122,6 +1099,7 @@ "window": "Okno", "yes": "Ano", "you": "Vy", + "yourCpuIsUnsupportedSSE42": "Váš procesor nepodporuje instrukce SSE 4.2, které jsou vyžadovány aplikací Session v operačních systémech Linux x64 pro zpracování obrázků. Proveďte upgrade na kompatibilní procesor nebo použijte jiný operační systém.", "yourRecoveryPassword": "Vaše heslo pro obnovení", "zoomFactor": "Měřítko přiblížení", "zoomFactorDescription": "Upravte velikost textu a vizuálních prvků." diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 5dff958b7..bd54c22f7 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -185,6 +185,7 @@ "callsSettings": "Anrufe (Beta)", "callsVoiceAndVideo": "Sprach- und Videoanrufe", "callsVoiceAndVideoBeta": "Sprach- und Videoanrufe (Beta)", + "callsVoiceAndVideoModalDescription": "Deine IP ist deinem Anrufpartner und einem Session Foundation Server sichtbar während Beta Anrufe getätigt werden.", "callsVoiceAndVideoToggleDescription": "Aktiviert Sprach- und Videoanrufe an und von anderen Benutzern.", "callsYouCalled": "Du hast {name} angerufen", "callsYouMissedCallPermissions": "Du hast einen Anruf von {name} verpasst, weil du Sprach- und Videoanrufe in den Datenschutzeinstellungen nicht aktiviert hast.", @@ -197,6 +198,7 @@ "cancel": "Abbrechen", "change": "Ändern", "changePasswordFail": "Passwortänderung fehlgeschlagen", + "changePasswordModalDescription": "Ändere dein Passwort für Session. Lokal gespeicherte Dateien werden mit deinem neuen Passwort entschlüsselt.", "clear": "Löschen", "clearAll": "Alles löschen", "clearDataAll": "Alle Daten löschen", @@ -294,6 +296,7 @@ "copy": "Kopieren", "create": "Erstellen", "creatingCall": "Anruf wird erstellt", + "currentPassword": "Aktuelles Passwort", "cut": "Ausschneiden", "darkMode": "Dunkelmodus", "databaseErrorClearDataWarning": "Möchtest du wirklich alle Nachrichten, Anhänge und Kontodaten von diesem Gerät löschen und ein neues Konto erstellen?", @@ -565,6 +568,7 @@ "linkPreviewsSendModalDescription": "Beim Senden von Link-Vorschauen hast du keinen vollständigen Metadatenschutz.", "linkPreviewsTurnedOff": "Link-Vorschauen sind aus", "linkPreviewsTurnedOffDescription": "Session muss verlinkte Webseiten kontaktieren, um Vorschauen von Links zu erstellen, die du sendest und empfängst.

Du kannst diese Funktion in den Session-Einstellungen aktivieren.", + "links": "Verknüpfungen", "loadAccount": "Account laden", "loadAccountProgressMessage": "Dein Account wird geladen", "loading": "Wird geladen...", @@ -577,6 +581,7 @@ "lockAppStatus": "Sperrstatus", "lockAppUnlock": "Tippe zum Entsperren", "lockAppUnlocked": "Session ist entsperrt", + "logs": "Protokolle", "manageMembers": "Mitglieder verwalten", "max": "Maximal", "media": "Medien", @@ -646,6 +651,7 @@ "modalMessageTooLongDescription": "Bitte kürze deine Nachricht auf {limit} Zeichen oder weniger.", "modalMessageTooLongTitle": "Nachricht zu lang", "networkName": "Session Network", + "newPassword": "Neues Passwort", "next": "Weiter", "nicknameDescription": "Wähle einen Spitznamen für {name}. Dieser wird dir in deinen Einzel- und Gruppengesprächen angezeigt.", "nicknameEnter": "Spitzname eingeben", @@ -660,7 +666,9 @@ "noteToSelfEmpty": "Du hast keine Nachrichten in »Notiz an mich«.", "noteToSelfHide": "»Notiz an mich« ausblenden", "noteToSelfHideDescription": "Bist du sicher, dass du »Notiz an mich« ausblenden möchtest?", + "notificationDisplay": "Benachrichtigungsanzeige", "notificationSenderNameAndPreview": "Zeigt den Namen des Absenders und eine Vorschau des Nachrichteninhalts an.", + "notificationSenderNameOnly": "Nur den Namen des Absenders ohne Nachrichteninhalt anzeigen.", "notificationsAllMessages": "Alle Nachrichten", "notificationsContent": "Benachrichtigungsinhalt", "notificationsContentDescription": "Der Inhalt, der in den Benachrichtigungen angezeigt wird.", @@ -744,6 +752,7 @@ "passwordIncorrect": "Falsches Passwort", "passwordNewConfirm": "Neues Passwort wiederholen", "passwordRemove": "Passwort entfernen", + "passwordRemoveShortDescription": "Entfernung des Passwortes erforderlich um Session zu entsperren", "passwordRemovedDescriptionToast": "Dein Passwort wurde entfernt.", "passwordSet": "Passwort festlegen", "passwordSetDescriptionToast": "Dein Passwort wurde festgelegt. Bitte bewahre es sicher auf.", @@ -752,6 +761,7 @@ "passwordStrengthIncludesLowercase": "Enthält einen Kleinbuchstaben", "passwordStrengthIncludesUppercase": "Enthält einen Großbuchstaben", "passwordStrengthIndicator": "Passwortstärke-Anzeige", + "passwordStrengthIndicatorDescription": "Ein schwieriges Passwort hilft deine Nachrichten und Anlagen zu schützen, wenn dein Gerät jemals verloren geht oder gestohlen wird.", "passwords": "Passwörter", "paste": "Einfügen", "permissionChange": "Berechtigungsänderung", @@ -803,9 +813,12 @@ "proAnimatedDisplayPictureFeature": "Animiertes Profilbild", "proAnimatedDisplayPictureModalDescription": "Nutzer können GIFs hochladen", "proAnimatedDisplayPicturesNonProModalDescription": "GIFs hochladen mit", + "proAutoRenewTime": "Automatische Pro Erneuerung in {time}", "proCallToActionLongerMessages": "Du möchtest längere Nachrichten senden? Sende mehr Text und schalte Premium-Funktionen mit Session Pro frei", "proCallToActionPinnedConversations": "Mehr Anheftungen gewünscht? Organisiere deine Chats und schalte Premium-Funktionen mit Session Pro frei", "proCallToActionPinnedConversationsMoreThan": "Mehr als 5 Anheftungen gewünscht? Organisiere deine Chats und schalte Premium-Funktionen mit Session Pro frei", + "proExpired": "Abgelaufen", + "proFaq": "Pro FAQ", "proFeatureListAnimatedDisplayPicture": "GIF- und WebP-Profilbilder hochladen", "proFeatureListLargerGroups": "Größere Gruppenchats mit bis zu 300 Mitgliedern", "proFeatureListLoadsMore": "Und viele weitere exklusive Funktionen", @@ -817,6 +830,8 @@ "proIncreasedMessageLengthFeature": "Erhöhte Nachrichtenlänge", "proMessageInfoFeatures": "Diese Nachricht verwendet die folgenden Session Pro-Funktionen:", "proSendMore": "Mehr senden mit", + "proStats": "Deine Pro Statistik", + "proTosPrivacy": "Durch die Aktualisierung stimmst du den Nutzungsbedingungen {icon} und der Datenschutzerklärung {icon} von Session Pro zu", "proUserProfileModalCallToAction": "Willst du mehr aus Session herausholen? Upgrade auf Session Pro für ein leistungsstärkeres Nachrichten-Erlebnis.", "profile": "Profil", "profileDisplayPicture": "Anzeigebild", @@ -873,6 +888,7 @@ "removePasswordFail": "Fehler beim Entfernen des Passworts", "removePasswordModalDescription": "Entferne dein aktuelles Passwort für Session. Lokal gespeicherte Daten werden mit einem zufällig generierten Schlüssel, der auf deinem Gerät gespeichert wird, erneut verschlüsselt.", "reply": "Antworten", + "requestRefund": "Rückerstattung anfordern", "resend": "Erneut senden", "resolving": "Länder werden geladen ...", "restart": "Neustart", @@ -928,10 +944,12 @@ "sessionNotifications": "Benachrichtigungen", "sessionPermissions": "Berechtigungen", "sessionPrivacy": "Datenschutz", + "sessionProBeta": "Session Pro Beta", "sessionRecoveryPassword": "Wiederherstellungspasswort", "sessionSettings": "Einstellungen", "set": "Speichern", "setCommunityDisplayPicture": "Community-Anzeigebild festlegen", + "setPasswordModalDescription": "Setze ein Passwort für Session. Lokal gespeicherte Dateien werden mit diesem Passwort verschlüsselt. Du wirst jedes Mal nach diesem Passwort gefragt, wenn du Session startest.", "settingsRestartDescription": "Du musst Session neu starten, um die neuen Einstellungen zu übernehmen.", "settingsScreenSecurityDesktop": "Bildschirmschutz", "share": "Teilen", diff --git a/_locales/el/messages.json b/_locales/el/messages.json index 81b6e2298..9e9ee7dd0 100644 --- a/_locales/el/messages.json +++ b/_locales/el/messages.json @@ -35,6 +35,7 @@ "adminRemovedUser": "{name} αφαιρέθηκε ως Διαχειριστής.", "adminRemovedUserMultiple": "{name} και {count} άλλοι αφαιρέθηκαν ως Διαχειριστές.", "adminRemovedUserOther": "{name} και {other_name} αφαιρέθηκαν ως Διαχειριστές.", + "adminSendingPromotion": "{count, plural, one [Στέλνεται προαγωγή διαχειριστή] other [Στέλνονται προαγωγές διαχειριστή]}", "adminSettings": "Ρυθμίσεις Διαχειριστή", "adminTwoPromotedToAdmin": "{name} και {other_name} προωθήθηκαν στο Admin.", "andMore": "+{count}", @@ -246,6 +247,7 @@ "copy": "Αντιγραφή", "create": "Δημιουργία", "cut": "Αποκοπή", + "databaseErrorGeneric": "Παρουσιάστηκε σφάλμα βάσης δεδομένων.

Εξαγάγετε τα αρχεία καταγραφής της εφαρμογής σας για κοινή χρήση στην αντιμετώπιση προβλημάτων. Αν αυτό δεν είναι επιτυχές, επανεγκαταστήστε το Session και επαναφέρετε τον λογαριασμό σας.", "databaseErrorTimeout": "Παρατηρήσαμε ότι το Session χρειάζεται πολύ χρόνο για να ξεκινήσει.

Μπορείτε να συνεχίσετε να περιμένετε, να εξάγετε τα αρχεία καταγραφής της συσκευής σας για να τα μοιραστείτε για την αντιμετώπιση προβλημάτων ή να επανεκκινήσετε το Session.", "databaseErrorUpdate": "Η βάση δεδομένων της εφαρμογής σας δεν είναι συμβατή με αυτήν την έκδοση του Session. Επανεγκαταστήστε την εφαρμογή και αποκαταστήστε τον λογαριασμό σας για να δημιουργήσετε μια νέα βάση δεδομένων και να συνεχίσετε να χρησιμοποιείτε το Session.

Προειδοποίηση: Αυτό θα έχει ως αποτέλεσμα την απώλεια όλων των μηνυμάτων και των συνημμένων που είναι παλαιότερα των δύο εβδομάδων.", "databaseOptimizing": "Βελτιστοποίηση Βάσης Δεδομένων", @@ -268,9 +270,11 @@ "deleteAfterLegacyGroupsGroupUpdateErrorTitle": "Αποτυχία Ενημέρωσης Ομάδας", "deleteAfterMessageDeletionStandardisationMessageDeletionForbidden": "Δεν έχετε άδεια να διαγράψετε τα μηνύματα άλλων", "deleteMessage": "{count, plural, one [Διαγραφή Μηνύματος] other [Διαγραφή Μηνυμάτων]}", + "deleteMessageConfirm": "{count, plural, one [Είστε σίγουρος ότι θέλετε να διαγράψετε αυτό το μήνυμα;] other [Είστε σίγουρος ότι θέλετε να διαγράψετε αυτά τα μηνύματα;]}", "deleteMessageDeleted": "{count, plural, one [Το μήνυμα διαγράφηκε] other [Τα μηνύματα διαγράφηκαν]}", "deleteMessageDeletedGlobally": "Αυτό το μήνυμα έχει διαγραφεί", "deleteMessageDeletedLocally": "Αυτό το μήνυμα έχει διαγραφεί σε αυτή τη συσκευή", + "deleteMessageDescriptionDevice": "{count, plural, one [Είστε σίγουρος ότι θέλετε να διαγράψετε αυτό το μήνυμα μόνο από αυτή τη συσκευή;] other [Είστε σίγουρος ότι θέλετε να διαγράψετε αυτά τα μηνύματα μόνο από αυτή τη συσκευή;]}", "deleteMessageDescriptionEveryone": "Σίγουρα θέλετε να διαγράψετε αυτό το μήνυμα για όλους;", "deleteMessageDeviceOnly": "Διαγραφή μόνο σε αυτή τη συσκευή", "deleteMessageDevicesAll": "Διαγραφή από όλες τις συσκευές μου", @@ -368,6 +372,7 @@ "groupCreate": "Δημιουργία Ομάδας", "groupCreateErrorNoMembers": "Παρακαλώ επιλέξτε τουλάχιστον 1 μέλος ομάδας.", "groupDelete": "Διαγραφή Ομάδας", + "groupDeleteDescription": "Είστε βέβαιοι ότι θέλετε να διαγράψετε το {group_name}?

Αυτό θα αφαιρέσει όλα τα μέλη και θα διαγράψει όλο το περιεχόμενο της ομάδας.", "groupDescriptionEnter": "Εισαγάγετε μια περιγραφή ομάδας", "groupDisplayPictureUpdated": "Η εικόνα προβολής της ομάδας ενημερώθηκε.", "groupEdit": "Επεξεργασία Ομάδας", @@ -382,6 +387,7 @@ "groupInviteFailedUser": "Αποτυχία πρόσκλησης {name} στο {group_name}", "groupInviteReinvite": "{name} σας προσκάλεσε να επανενωθείτε με την ομάδα {group_name}, όπου είστε Διαχειριστής.", "groupInviteReinviteYou": "Προσκληθήκατε να επανενταχθείτε στην ομάδα {group_name}, όπου είστε διαχειριστής.", + "groupInviteSending": "{count, plural, one [Στέλνεται πρόσκληση] other [Στέλνονται προσκλήσεις]}", "groupInviteSent": "Η πρόσκληση στάλθηκε", "groupInviteSuccessful": "Η πρόσκληση στην ομάδα ήταν επιτυχής", "groupInviteVersion": "Οι χρήστες πρέπει να έχουν την τελευταία έκδοση για να λάβουν προσκλήσεις", @@ -447,6 +453,8 @@ "incognitoKeyboardDescription": "Ζητήστε ανώνυμη λειτουργία αν είναι διαθέσιμη. Ανάλογα με το πληκτρολόγιο που χρησιμοποιείτε, το πληκτρολόγιο σας μπορεί να αγνοήσει αυτό το αίτημα.", "info": "Πληροφορίες", "invalidShortcut": "Μη έγκυρη συντόμευση", + "inviteFailed": "{count, plural, one [Η πρόσκληση απέτυχε] other [Οι προσκλήσεις απέτυχαν]}", + "inviteFailedDescription": "{count, plural, one [Η πρόσκληση δεν μπορούσε να σταλθεί. Θέλετε να προσπαθήσετε ξανά;] other [Οι προσκλήσεις δεν μπορούσαν να σταλθούν. Θέλετε να προσπαθήσετε ξανά;]}", "join": "Γίνετε μέλος", "later": "Αργότερα", "learnMore": "Μάθετε Περισσότερα", @@ -506,6 +514,7 @@ "messageNewDescriptionDesktop": "Ξεκινήστε μια νέα συνομιλία εισάγοντας το ID λογαριασμού του φίλου σας ή το ONS.", "messageNewDescriptionMobile": "Ξεκινήστε μια νέα συνομιλία εισάγοντας το ID λογαριασμού του φίλου σας, το ONS ή σκανάροντας τον κώδικα QR τους.", "messageNewYouveGot": "{count, plural, one [Έχετε νέο μήνυμα.] other [Έχετε {count} νέα μηνύματα.]}", + "messageNewYouveGotGroup": "{count, plural, one [Έχετε νέο μήνυμα στο {group_name}.] other [Έχετε {count} νέα μηνύματα στο {group_name}.]}", "messageReplyingTo": "Απάντηση σε", "messageRequestGroupInvite": "{name} σας προσκάλεσε να συμμετάσχετε στην ομάδα {group_name}.", "messageRequestGroupInviteDescription": "Η αποστολή μηνύματος σε αυτή την ομάδα θα αποδεχτεί αυτόματα την πρόσκληση της ομάδας.", @@ -534,6 +543,7 @@ "messageVoiceSnippetGroup": "{author}: {emoji} Ηχητικό μήνυμα", "messages": "Μηνύματα", "minimize": "Ελαχιστοποίηση", + "modalMessageCharacterDisplayDescription": "{count, plural, one [Τα μηνύματα έχουν όριο {limit} χαρακτήρων. Σας απομένει {count} χαρακτήρας.] other [Τα μηνύματα έχουν όριο {limit} χαρακτήρων. Σας απομένουν {count} χαρακτήρες.]}", "networkName": "Session Network", "next": "Επόμενο", "nicknameDescription": "Επιλέξτε ένα ψευδώνυμο για {name}. Αυτό θα εμφανιστεί σε εσάς στις προσωπικές και ομαδικές συνομιλίες σας.", @@ -630,6 +640,7 @@ "permissionsCameraDenied": "Το Session χρειάζεται πρόσβαση στην κάμερα για τη λήψη φωτογραφιών και βίντεο, αλλά έχει απορριφθεί μόνιμα. Πατήστε Ρυθμίσεις → Άδειες, και ενεργοποιήστε την \"Κάμερα\".", "permissionsFaceId": "Η λειτουργία κλειδώματος οθόνης στο Session χρησιμοποιεί το Face ID.", "permissionsKeepInSystemTray": "Διατήρηση στην Περιοχή Ειδοποιήσεων", + "permissionsKeepInSystemTrayDescription": "Το Session συνεχίζει να εκτελείται στο παρασκήνιο όταν κλείνετε το παράθυρο.", "permissionsLibrary": "Το Session χρειάζεται πρόσβαση στη φωτογραφική βιβλιοθήκη για να συνεχίσει. Μπορείτε να ενεργοποιήσετε την πρόσβαση στις ρυθμίσεις του iOS.", "permissionsMicrophone": "Μικρόφωνο", "permissionsMicrophoneAccessRequired": "Session χρειάζεται πρόσβαση στο μικρόφωνο για να πραγματοποιήσει κλήσεις και να στείλει ηχητικά μηνύματα, αλλά έχει απορριφθεί μόνιμα. Πατήστε Ρυθμίσεις → Άδειες, και ενεργοποιήστε το \"Μικρόφωνο\".", @@ -649,6 +660,10 @@ "pinUnpinConversation": "Ξεκαρφίτσωμα Συνομιλίας", "preview": "Προεπισκόπηση", "pro": "Pro", + "proBadgesSent": "{count, plural, one [{total} Pro Σήμα στάλθηκε] other [{total} Pro Εμβλήματα στάλθηκαν]}", + "proGroupsUpgraded": "{count, plural, one [Αναβαθμίστηκε η {total} ομάδα] other [Αναβαθμίστηκαν {total} ομάδες]}", + "proLongerMessagesSent": "{count, plural, one [{total} Μεγαλύτερο Μήνυμα εστάλη] other [{total} Μεγαλύτερο Μήνυμα εστάλησαν]}", + "proPinnedConversations": "{count, plural, one [{total} Καρφιτσωμένη Συνομιλία] other [{total} Καρφιτσωμένες Συνομιλίες]}", "profile": "Προφίλ", "profileDisplayPicture": "Display Picture", "profileDisplayPictureRemoveError": "Αποτυχία κατάργησης εικόνας εμφάνισης.", @@ -656,6 +671,8 @@ "profileDisplayPictureSizeError": "Παρακαλώ επιλέξτε ένα μικρότερο αρχείο.", "profileErrorUpdate": "Αποτυχία ενημέρωσης προφίλ.", "promote": "Προώθηση", + "promotionFailed": "{count, plural, one [Η προώθηση απέτυχε] other [Οι προωθήσεις απέτυχαν]}", + "promotionFailedDescription": "{count, plural, one [Η προώθηση δεν ήταν δυνατό να εφαρμοστεί. Θέλετε να προσπαθήσετε ξανά;] other [Οι προωθήσεις δεν ήταν δυνατό να εφαρμοστούν. Θέλετε να προσπαθήσετε ξανά;]}", "qrCode": "Κωδικός QR", "qrNotAccountId": "Αυτός ο κωδικός QR δεν περιέχει ID λογαριασμού", "qrNotRecoveryPassword": "Αυτός ο κωδικός QR δεν περιέχει κωδικό ανάκτησης", @@ -684,6 +701,7 @@ "recoveryPasswordRestoreDescription": "Εισαγάγετε τον κωδικό σας ανάκτησης για να φορτώσετε τον λογαριασμό σας. Αν δεν τον έχετε αποθηκεύσει, μπορείτε να τον βρείτε στις ρυθμίσεις της εφαρμογής.", "recoveryPasswordWarningSendDescription": "Αυτός είναι ο κωδικός ανάκτησής σας. Αν τον στείλετε σε κάποιον, θα έχει πλήρη πρόσβαση στον λογαριασμό σας.", "redo": "Επανάληψη", + "remainingCharactersTooltip": "{count, plural, one [{count} χαρακτήρας απομένει] other [{count} χαρακτήρες απομένουν]}", "remove": "Αφαίρεση", "removePasswordFail": "Αποτυχία κατάργησης κωδικού πρόσβασης", "reply": "Απάντηση", @@ -760,6 +778,7 @@ "updateError": "Αδυναμία Ενημέρωσης", "updateErrorDescription": "Το Session απέτυχε να ενημερωθεί. Παρακαλώ πηγαίνετε στο https://getsession.org/download και εγκαταστήστε τη νέα έκδοση χειροκίνητα, στη συνέχεια επικοινωνήστε με το Κέντρο Βοήθειας για να μας ενημερώσετε για αυτό το πρόβλημα.", "updateNewVersion": "Μια νέα έκδοση της εφαρμογής Session είναι διαθέσιμη, πατήστε για ενημέρωση", + "updateNewVersionDescription": "Μια νέα έκδοση ({version}) της εφαρμογής Session είναι διαθέσιμη.", "updateReleaseNotes": "Μετάβαση στις Σημειώσεις Έκδοσης", "updateSession": "Ενημέρωση Session", "updateVersion": "Έκδοση {version}", diff --git a/_locales/en/messages.json b/_locales/en/messages.json index a7f3ecc60..94500b40f 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -189,6 +189,10 @@ "callsVoiceAndVideoToggleDescription": "Enables voice and video calls to and from other users.", "callsYouCalled": "You called {name}", "callsYouMissedCallPermissions": "You missed a call from {name} because you haven't enabled Voice and Video Calls in Privacy Settings.", + "cameraAccessDeniedMessage": "Session needs access to your camera to enable video calls, but this permission has been denied. You can’t update your camera permissions during a call.

Would you like to end the call now and enable camera access, or would you like to be reminded after the call?", + "cameraAccessInstructions": "To allow camera access, open settings and turn on the Camera permission.", + "cameraAccessReminderMessage": "During your last call, you tried to use video but couldn’t because camera access was previously denied. To allow camera access, open settings and turn on the Camera permission.", + "cameraAccessRequired": "Camera Access Required", "cameraErrorNotFound": "No camera found", "cameraErrorUnavailable": "Camera unavailable.", "cameraGrantAccess": "Grant Camera Access", @@ -196,12 +200,18 @@ "cameraGrantAccessDescription": "Session needs camera access to take photos and videos, or scan QR codes.", "cameraGrantAccessQr": "Session needs camera access to scan QR codes", "cancel": "Cancel", - "cancelPlan": "Cancel Plan", + "cancelAccess": "Cancel Pro", + "cancelProPlan": "Cancel Pro Plan", + "cancelProPlatform": "Cancel on the {platform} website, using the {platform_account} you signed up for Pro with.", + "cancelProPlatformStore": "Cancel on the {platform_store} website, using the {platform_account} you signed up for Pro with.", "change": "Change", "changePasswordFail": "Failed to change password", "changePasswordModalDescription": "Change your password for Session. Locally stored data will be re-encrypted with your new password.", "checkingProStatus": "Checking Pro Status", - "checkingProStatusDescription": "Checking your Pro details. Some information on this page may be inaccurate until this check is complete.", + "checkingProStatusContinue": "Checking your Pro status. You'll be able to continue once this check is complete.", + "checkingProStatusDescription": "Checking your Pro details. Some information on this page may be unavailable until this check is complete.", + "checkingProStatusEllipsis": "Checking Pro Status...", + "checkingProStatusRenew": "Checking your Pro details. You cannot renew until this check is complete.", "checkingProStatusUpgradeDescription": "Checking your Pro status. You'll be able to upgrade to Pro once this check is complete.", "clear": "Clear", "clearAll": "Clear All", @@ -303,8 +313,8 @@ "copy": "Copy", "create": "Create", "creatingCall": "Creating Call", + "currentBilling": "Current Billing", "currentPassword": "Current Password", - "currentPlan": "Current Plan", "cut": "Cut", "darkMode": "Dark Mode", "databaseErrorClearDataWarning": "Are you sure you want to delete all messages, attachments, and account data from this device and create a new account?", @@ -331,6 +341,8 @@ "deleteAfterLegacyGroupsGroupCreation": "Please wait while the group is created...", "deleteAfterLegacyGroupsGroupUpdateErrorTitle": "Failed to Update Group", "deleteAfterMessageDeletionStandardisationMessageDeletionForbidden": "You don’t have permission to delete others’ messages", + "deleteAttachments": "{count, plural, one [Delete Selected Attachment] other [Delete Selected Attachments]}", + "deleteAttachmentsDescription": "{count, plural, one [Are you sure you want to delete the selected attachment? The message associated with the attachment will also be deleted.] other [Are you sure you want to delete the selected attachments? The message associated with the attachments will also be deleted.]}", "deleteContactDescription": "Are you sure you want to delete {name} from your contacts?

This will delete your conversation, including all messages and attachments. Future messages from {name} will appear as a message request.", "deleteConversationDescription": "Are you sure you want to delete your conversation with {name}?
This will permanently delete all messages and attachments.", "deleteMessage": "{count, plural, one [Delete Message] other [Delete Messages]}", @@ -422,18 +434,22 @@ "emojiReactsHoverYouNameTwoDesktop": "You and {name} reacted with {emoji_name}", "emojiReactsNotification": "Reacted to your message {emoji}", "enable": "Enable", + "enableCameraAccess": "Enable Camera Access?", "enableNotifications": "Show notifications when you receive new messages.", + "endCallToEnable": "End Call to Enable", "enjoyingSession": "Enjoying Session?", "enjoyingSessionButtonNegative": "Needs Work {emoji}", "enjoyingSessionButtonPositive": "It's Great {emoji}", "enjoyingSessionDescription": "You've been using Session for a little while, how’s it going? We’d really appreciate hearing your thoughts.", "enter": "Enter", - "errorCheckingProStatus": "Error checking Pro status.", + "enterPasswordDescription": "Enter the password you set for Session", + "enterPasswordTooltip": "Enter the password you use to unlock Session on startup, not your Recovery Password", + "errorCheckingProStatus": "Error checking Pro status", "errorConnection": "Please check your internet connection and try again.", "errorCopyAndQuit": "Copy Error and Quit", "errorDatabase": "Database Error", "errorGeneric": "Something went wrong. Please try again later.", - "errorLoadingProPlan": "Error loading Pro plan.", + "errorLoadingProAccess": "Error loading Pro access", "errorUnknown": "An unknown error occurred.", "failedToDownload": "Failed to download", "failures": "Failures", @@ -677,6 +693,8 @@ "nicknameSet": "Set Nickname", "no": "No", "noSuggestions": "No Suggestions", + "nonProLongerMessagesDescription": "Send messages up to 10,000 characters in all conversations.", + "nonProUnlimitedPinnedDescription": "Organize chats with unlimited pinned conversations.", "none": "None", "notNow": "Not now", "noteToSelf": "Note to Self", @@ -727,7 +745,11 @@ "okay": "Okay", "on": "On", "onDevice": "On your {device_type} device", - "onDeviceDescription": "Open this Session account on an {device_type} device logged into the {platform_account} you originally signed up with. Then, change your plan via the Session Pro settings.", + "onDeviceCancelDescription": "Open this Session account on an {device_type} device logged into the {platform_account} you originally signed up with. Then, cancel Pro via the Session Pro settings.", + "onDeviceDescription": "Open this Session account on an {device_type} device logged into the {platform_account} you originally signed up with. Then, update your Pro access via the Session Pro settings.", + "onLinkedDevice": "On a linked device", + "onPlatformStoreWebsite": "On the {platform_store} website", + "onPlatformWebsite": "On the {platform} website", "onboardingAccountCreate": "Create account", "onboardingAccountCreated": "Account Created", "onboardingAccountExists": "I have an account", @@ -752,7 +774,9 @@ "onsErrorNotRecognized": "We couldn't recognize this ONS. Please check it and try again.", "onsErrorUnableToSearch": "We were unable to search for this ONS. Please try again later.", "open": "Open", - "openStoreWebsite": "Open {platform_store} Website", + "openPlatformStoreWebsite": "Open {platform_store} Website", + "openPlatformWebsite": "Open {platform} Website", + "openSettings": "Open Settings", "openSurvey": "Open Survey", "other": "Other", "oxenFoundation": "Oxen Foundation", @@ -831,9 +855,30 @@ "preview": "Preview", "previewNotification": "Preview Notification", "pro": "Pro", + "proAccessActivatedAutoShort": "Your Pro access is active!

Your Pro access will automatically renew for another {current_plan_length} on {date}.", + "proAccessActivatedNotAuto": "Your Pro access will expire on {date}.

Update your Pro access now to ensure you automatically renew before your Pro access expires.", + "proAccessActivatesAuto": "Your Pro access is active!

Your Pro access will automatically renew for another
{current_plan_length} on {date}. Any updates you make here will take effect at your next renewal.", + "proAccessError": "Pro Access Error", + "proAccessExpireDate": "Your Pro access will expire on {date}.", + "proAccessLoading": "Pro Access Loading", + "proAccessLoadingDescription": "Your Pro access information is still being loaded. You cannot update until this process is complete.", + "proAccessLoadingEllipsis": "Pro access loading...", + "proAccessNetworkLoadError": "Unable to connect to the network to load your Pro access information. Updating Pro via Session will be disabled until connectivity is restored.

Please check your network connection and retry.", + "proAccessNotFound": "Pro Access Not Found", + "proAccessNotFoundDescription": "Session detected that your account does not have Pro access. If you believe this is a mistake, please reach out to Session support for assistance.", + "proAccessRecover": "Recover Pro Access", + "proAccessRenew": "Renew Pro Access", + "proAccessRenewDesktop": "Currently, Pro access can only be purchased and renewed via the {platform_store} or {platform_store_other}. Because you are using Session Desktop, you're not able to renew here.

Session developers are working hard on alternative payment options to allow users to purchase Pro access outside of the {platform_store} and {platform_store_other}. Pro Roadmap {icon}", + "proAccessRenewPlatformStoreWebsite": "Renew your Pro access on the {platform_store} website using the {platform_account} you signed up for Pro with.", + "proAccessRenewPlatformWebsite": "Renew on the {platform} website using the {platform_account} you signed up for Pro with.", + "proAccessRenewStart": "Renew your Pro access to start using powerful Session Pro Beta features again.", + "proAccessRestored": "Pro Access Recovered", + "proAccessRestoredDescription": "Session detected and recovered Pro access for your account. Your Pro status has been restored!", + "proAccessSignUp": "Because you originally signed up for Session Pro via the {platform_store}, you'll need to use your {platform_account} to update your Pro access.", + "proAccessUpgradeDesktop": "Currently, Pro access can only be purchased via the {platform_store} or {platform_store_other}. Because you are using Session Desktop, you're not able to upgrade to Pro here.

Session developers are working hard on alternative payment options to allow users to purchase Pro access outside of the {platform_store} and {platform_store_other}. Pro Roadmap {icon}", "proActivated": "Activated", "proAllSet": "You're all set!", - "proAllSetDescription": "Your Session Pro plan was updated! You will be billed when your current Pro plan is automatically renewed on {date}.", + "proAllSetDescription": "Your Session Pro access was updated! You will be billed when Pro is automatically renewed on {date}.", "proAlreadyPurchased": "You’ve already got", "proAnimatedDisplayPicture": "Go ahead and upload GIFs and animated WebP images for your display picture!", "proAnimatedDisplayPictureCallToActionDescription": "Get animated display pictures and unlock premium features with Session Pro", @@ -848,77 +893,84 @@ "proBadges": "Badges", "proBadgesDescription": "Show your support for Session with an exclusive badge next to your display name.", "proBadgesSent": "{count, plural, one [{total} Pro Badge Sent] other [{total} Pro Badges Sent]}", + "proBetaFeatures": "Pro Beta Features", "proBilledAnnually": "{price} Billed Annually", "proBilledMonthly": "{price} Billed Monthly", "proBilledQuarterly": "{price} Billed Quarterly", "proCallToActionLongerMessages": "Want to send longer messages? Send more text and unlock premium features with Session Pro", "proCallToActionPinnedConversations": "Want more pins? Organize your chats and unlock premium features with Session Pro", "proCallToActionPinnedConversationsMoreThan": "Want more than 5 pins? Organize your chats and unlock premium features with Session Pro", - "proDiscountTooltip": "Your current plan is already discounted by {percent}% of the full Session Pro price.", + "proCancelSorry": "Sorry to see you cancel Pro. Here's what you need to know before canceling your Pro access.", + "proCancellation": "Cancellation", + "proCancellationDescription": "Canceling Pro access will prevent automatic renewal from occurring before Pro access expires. Canceling Pro does not result in a refund. You will continue to be able to use Session Pro features until your Pro access expires.

Because you originally signed up for Session Pro using your {platform_account}, you'll need to use the same {platform_account} to cancel Pro.", + "proCancellationOptions": "Two ways to cancel your Pro access:", + "proCancellationShortDescription": "Canceling Pro access will prevent will prevent automatic renewal from occurring before Pro expires.

Canceling Pro does not result in a refund. You will continue to be able to use Session Pro features until your Pro access expires.", + "proChooseAccess": "Choose the Pro access option that's right for you.
Longer access means bigger discounts.", + "proClearAllDataDevice": "Are you sure you want to delete your data from this device?

Session Pro cannot be transferred to another account. Please save your Recovery Password to ensure you can restore your Pro access later.", + "proClearAllDataNetwork": "Are you sure you want to delete your data from the network? If you continue, you will not be able to restore your messages or contacts.

Session Pro cannot be transferred to another account. Please save your Recovery Password to ensure you can restore your Pro access later.", + "proDiscountTooltip": "Your Pro access is already discounted by {percent}% of the full Session Pro price.", "proErrorRefreshingStatus": "Error refreshing Pro status", "proExpired": "Expired", - "proExpiredDescription": "Unfortunately, your Pro plan has expired. Renew to keep accessing the exclusive perks and features of Session Pro.", + "proExpiredDescription": "Unfortunately, your Pro access has expired. Renew to reactivate the exclusive perks and features of Session Pro.", "proExpiringSoon": "Expiring Soon", - "proExpiringSoonDescription": "Your Pro plan is expiring in {time}. Update your plan to keep accessing the exclusive perks and features of Session Pro.", + "proExpiringSoonDescription": "Your Pro access is expiring in {time}. Update now to keep accessing the exclusive perks and features of Session Pro.", "proExpiringTime": "Pro expiring in {time}", "proFaq": "Pro FAQ", - "proFaqDescription": "Find answers to common questions in the Session FAQ.", + "proFaqDescription": "Find answers to common questions in the Session Pro FAQ.", "proFeatureListAnimatedDisplayPicture": "Upload GIF and WebP display pictures", "proFeatureListLargerGroups": "Larger group chats up to 300 members", "proFeatureListLoadsMore": "Plus loads more exclusive features", "proFeatureListLongerMessages": "Messages up to 10,000 characters", "proFeatureListPinnedConversations": "Pin unlimited conversations", - "proFeatures": "Pro Features", + "proFullestPotential": "Want to use Session to its fullest potential?
Upgrade to Session Pro Beta to get access to loads of exclusive perks and features.", "proGroupActivated": "Group Activated", "proGroupActivatedDescription": "This group has expanded capacity! It can support up to 300 members because a group admin has", "proGroupsUpgraded": "{count, plural, one [{total} Group Upgraded] other [{total} Groups Upgraded]}", - "proImportantDescription": "Requesting a refund is final. If approved, your Pro plan will be canceled immediately and you will lose access to all Pro features.", + "proImportantDescription": "Requesting a refund is final. If approved, your Pro access will be canceled immediately and you will lose access to all Pro features.", "proIncreasedAttachmentSizeFeature": "Increased Attachment Size", "proIncreasedMessageLengthFeature": "Increased Message Length", "proLargerGroups": "Larger Groups", "proLargerGroupsDescription": "Groups you are an admin in are automatically upgraded to support 300 members.", + "proLargerGroupsTooltip": "Larger group chats (up to 300 members) are coming soon for all Pro Beta users!", "proLongerMessages": "Longer Messages", "proLongerMessagesDescription": "You can send messages up to 10,000 characters in all conversations.", "proLongerMessagesSent": "{count, plural, one [{total} Longer Message Sent] other [{total} Longer Messages Sent]}", "proMessageInfoFeatures": "This message used the following Session Pro features:", + "proNewInstallation": "With a new installation", + "proNewInstallationDescription": "Reinstall Session on this device via the {platform_store}, restore your account with your Recovery Password, and renew Pro from the Session Pro settings.", + "proNewInstallationUpgrade": "Reinstall Session on this device via the {platform_store}, restore your account with your Recovery Password, and upgrade to Pro from the Session Pro settings.", + "proOptionsRenewalSubtitle": "For now, there are three ways to renew:", + "proOptionsTwoRenewalSubtitle": "For now, there are two ways to renew:", "proPercentOff": "{percent}% Off", "proPinnedConversations": "{count, plural, one [{total} Pinned Conversation] other [{total} Pinned Conversations]}", - "proPlanActivatedAuto": "Your Session Pro plan is active!

Your plan will automatically renew for another {current_plan} on {date}. Updates to your plan take effect when Pro is next renewed.", - "proPlanActivatedAutoShort": "Your Session Pro plan is active!

Your plan will automatically renew for another {current_plan} on {date}.", - "proPlanActivatedNotAuto": "Your Session Pro plan will expire on {date}.

Update your plan now to ensure uninterrupted access to exclusive Pro features.", - "proPlanError": "Pro Plan Error", - "proPlanExpireDate": "Your Session Pro plan will expire on {date}.", - "proPlanLoading": "Pro Plan Loading", - "proPlanLoadingDescription": "Information about your Pro plan is still being loaded. You cannot update your plan until this process is complete.", - "proPlanLoadingEllipsis": "Pro plan loading...", - "proPlanNetworkLoadError": "Unable to connect to the network to load your current plan. Updating your plan via Session will be disabled until connectivity is restored.

Please check your network connection and retry.", - "proPlanNotFound": "Pro Plan Not Found", - "proPlanNotFoundDescription": "No active plan was found for your account. If you believe this is a mistake, please reach out to Session support for assistance.", - "proPlanPlatformRefund": "Because you originally signed up for Session Pro via the {platform_store} Store, you'll need to use the same {platform_account} to request a refund.", - "proPlanPlatformRefundLong": "Because you originally signed up for Session Pro via the {platform_store} Store, your refund request will be processed by Session Support.

Request a refund by hitting the button below and completing the refund request form.

While Session Support strives to process refund requests within 24-72 hours, processing may take longer during times of high request volume.", - "proPlanRecover": "Recover Pro Plan", - "proPlanRenew": "Renew Pro Plan", - "proPlanRenewDesktop": "Currently, Pro plans can only be purchased and renewed via the {platform_store} or {platform_store} Stores. Because you are using Session Desktop, you're not able to renew your plan here.

Session Pro developers are working hard on alternative payment options to allow users to purchase Pro plans outside of the {platform_store} and {platform_store} Stores. Pro Roadmap", - "proPlanRenewDesktopLinked": "Renew your plan in the Session Pro settings on a linked device with Session installed via the {platform_store} or {platform_store} Store.", - "proPlanRenewDesktopStore": "Renew your plan on the {platform_store} website using the {platform_account} you signed up for Pro with.", - "proPlanRenewStart": "Renew your Session Pro plan to start using powerful Session Pro Beta features again.", - "proPlanRenewSupport": "Your Session Pro plan has been renewed! Thank you for supporting the Session Network.", - "proPlanRestored": "Pro Plan Restored", - "proPlanRestoredDescription": "A valid plan for Session Pro was detected and your Pro status has been restored!", - "proPlanSignUp": "Because you originally signed up for Session Pro via the {platform_store} Store, you'll need to use your {platform_account} to update your plan.", + "proPlanPlatformRefund": "Because you originally signed up for Session Pro via the {platform_store}, you'll need to use your {platform_account} to request a refund.", + "proPlanPlatformRefundLong": "Because you originally signed up for Session Pro via the {platform_store}, your refund request will be processed by Session Support.

Request a refund by hitting the button below and completing the refund request form.

While Session Support strives to process refund requests within 24-72 hours, processing may take longer during times of high request volume.", + "proPlanRenewSupport": "Your Session Pro access has been renewed! Thank you for supporting the Session Network.", "proPriceOneMonth": "1 Month - {monthly_price} / Month", "proPriceThreeMonths": "3 Months - {monthly_price} / Month", "proPriceTwelveMonths": "12 Months - {monthly_price} / Month", + "proRefundAccountDevice": "Open this Session account on an {device_type} device logged into the {platform_account} you originally signed up with. Then, request a refund via the Session Pro settings.", "proRefundDescription": "We’re sorry to see you go. Here's what you need to know before requesting a refund.", - "proRefundNextSteps": "{platform_account} is now processing your refund request. This typically takes 24-48 hours. Depending on their decision, you may see your Pro status change in Session.", + "proRefundNextSteps": "{platform} is now processing your refund request. This typically takes 24-48 hours. Depending on their decision, you may see your Pro status change in Session.", "proRefundRequestSessionSupport": "Your refund request will be handled by Session Support.

Request a refund by hitting the button below and completing the refund request form.

While Session Support strives to process refund requests within 24-72 hours, processing may take longer during times of high request volume.", - "proRefundRequestStorePolicies": "Your refund request will be handled exclusively by {platform_account} through the {platform_account} website.

Due to {platform_account} refund policies, Session developers have no ability to influence the outcome of refund requests. This includes whether the request is approved or denied, as well as whether a full or partial refund is issued.", - "proRefundSupport": "Please contact {platform_account} for further updates on your refund request. Due to {platform_account} refund policies, Session developers have no ability to influence the outcome of refund requests.

{platform_store} Refund Support", + "proRefundRequestStorePolicies": "Your refund request will be handled exclusively by {platform} through the {platform} website.

Due to {platform} refund policies, Session developers have no ability to influence the outcome of refund requests. This includes whether the request is approved or denied, as well as whether a full or partial refund is issued.", + "proRefundSupport": "Please contact {platform} for further updates on your refund request. Due to {platform} refund policies, Session developers have no ability to influence the outcome of refund requests.

{platform} Refund Support", "proRefunding": "Refunding Pro", - "proRefundingDescription": "Refunds for Session Pro plans are handled exclusively by {platform_account} through the {platform_store} Store.

Due to {platform_account} refund policies, Session developers have no ability to influence the outcome of refund requests. This includes whether the request is approved or denied, as well as whether a full or partial refund is issued.", + "proRefundingDescription": "Refunds for Session Pro are handled exclusively by {platform} through the {platform_store}.

Due to {platform} refund policies, Session developers have no ability to influence the outcome of refund requests. This includes whether the request is approved or denied, as well as whether a full or partial refund is issued.", + "proRenewAnimatedDisplayPicture": "Want to use animated display pictures again?
Renew your Pro access to unlock the features you’ve been missing out on.", + "proRenewBeta": "Renew Pro Beta", + "proRenewDesktopLinked": "Renew your Pro access from the Session Pro settings on a linked device with Session installed via the {platform_store} or {platform_store_other}.", + "proRenewLongerMessages": "Want to send longer messages again?
Renew your Pro access to unlock the features you’ve been missing out on.", + "proRenewMaxPotential": "Want to use Session to its max potential again?
Renew your Pro access to unlock the features you’ve been missing out on.", + "proRenewPinFiveConversations": "Want to pin more than 5 conversations again?
Renew your Pro access to unlock the features you’ve been missing out on.", + "proRenewPinMoreConversations": "Want to more pin more conversations again?
Renew your Pro access to unlock the features you’ve been missing out on.", + "proRenewTosPrivacy": "By renewing, you agree to the Session Pro Terms of Service {icon} and Privacy Policy {icon}", + "proRenewalUnsuccessful": "Pro renewal unsuccessful, retrying soon", + "proRenewingNoAccessBilling": "Currently, Pro access can only be purchased and renewed via the {platform_store} or {platform_store_other}. Because you installed Session using the {build_variant}, you're not able to renew here.

Session developers are working hard on alternative payment options to allow users to purchase Pro access outside of the {platform_store} and {platform_store_other}. Pro Roadmap {icon}", "proRequestedRefund": "Refund Requested", "proSendMore": "Send more with", "proSettings": "Pro Settings", + "proStartUsing": "Start Using Pro", "proStats": "Your Pro Stats", "proStatsLoading": "Pro Stats Loading", "proStatsLoadingDescription": "Your Pro stats are loading, please wait.", @@ -928,16 +980,26 @@ "proStatusLoading": "Pro Status Loading", "proStatusLoadingDescription": "Your Pro information is being loaded. Some actions on this page may be unavailable until loading is complete.", "proStatusLoadingSubtitle": "Pro status loading", + "proStatusNetworkErrorContinue": "Unable to connect to the network to check your Pro status. You cannot continue until connectivity is restored.

Please check your network connection and retry.", "proStatusNetworkErrorDescription": "Unable to connect to the network to check your Pro status. You cannot upgrade to Pro until connectivity is restored.

Please check your network connection and retry.", "proStatusRefreshNetworkError": "Unable to connect to the network to refresh your Pro status. Some actions on this page will be disabled until connectivity is restored.

Please check your network connection and retry.", - "proSupportDescription": "Need help with your Pro plan? Submit a request to the support team.", + "proStatusRenewError": "Unable to connect to the network to load your current Pro access. Renewing Pro via Session will be disabled until connectivity is restored.

Please check your network connection and retry.", + "proSupportDescription": "Need help with Pro? Submit a request to the support team.", "proTosPrivacy": "By updating, you agree to the Session Pro Terms of Service {icon} and Privacy Policy {icon}", "proUnlimitedPins": "Unlimited Pins", "proUnlimitedPinsDescription": "Organize all your chats with unlimited pinned conversations.", - "proUpdatePlanDescription": "You are currently on the {current_plan} Plan. Are you sure you want to switch to the {selected_plan} Plan?

By updating, your plan will automatically renew on {date} for an additional {selected_plan} of Pro access.", - "proUpdatePlanExpireDescription": "Your plan will expire on {date}.

By updating, your plan will automatically renew on {date} for an additional {selected_plan} of Pro access.", + "proUpdateAccessDescription": "Your current billing option grants {current_plan_length} of Pro access. Are you sure you want to switch to the {selected_plan_length_singular} billing option?

By updating, your Pro access will automatically renew on {date} for an additional {selected_plan_length} of Pro access.", + "proUpdateAccessExpireDescription": "Your Pro access will expire on {date}.

By updating, your Pro access will automatically renew on {date} for an additional {selected_plan_length} of Pro access.", + "proUpgradeAccess": "Upgrade to Session Pro Beta to get access to loads of exclusive perks and features.", + "proUpgradeDesktopLinked": "Upgrade to Pro from the Session Pro settings on a linked device with Session installed via the {platform_store} or {platform_store_other}.", + "proUpgradeNoAccessBilling": "Currently, Pro access can only be purchased via the {platform_store} or {platform_store_other}. Because you installed Session using the {build_variant}, you're not able to upgrade to Pro here.

Session developers are working hard on alternative payment options to allow users to purchase Pro access outside of the {platform_store} and {platform_store_other}. Pro Roadmap {icon}", + "proUpgradeOption": "For now, there is only one way to upgrade:", + "proUpgradeOptionsTwo": "For now, there are two ways to upgrade:", + "proUpgraded": "You have upgraded to Session Pro!

Thank you for supporting the Session Network.", + "proUpgradingTo": "Upgrading to Pro", + "proUpgradingTosPrivacy": "By upgrading, you agree to the Session Pro Terms of Service {icon} and Privacy Policy {icon}", "proUserProfileModalCallToAction": "Want to get more out of Session? Upgrade to Session Pro for a more powerful messaging experience.", - "processingRefundRequest": "{platform_account} is processing your refund request", + "processingRefundRequest": "{platform} is processing your refund request", "profile": "Profile", "profileDisplayPicture": "Display Picture", "profileDisplayPictureRemoveError": "Failed to remove display picture.", @@ -987,15 +1049,19 @@ "recoveryPasswordWarningSendDescription": "This is your recovery password. If you send it to someone they'll have full access to your account.", "recreateGroup": "Recreate Group", "redo": "Redo", - "refundPlanNonOriginatorApple": "Because you originally signed up for Session Pro via a different {platform_account}, you'll need to use that {platform_account} to update your plan.", + "refundNonOriginatorApple": "Because you originally signed up for Session Pro via a different {platform_account}, you'll need to use that {platform_account} to update your Pro access.", + "refundRequestOptions": "Two ways to request a refund:", "remainingCharactersOverTooltip": "Reduce message length by {count}", "remainingCharactersTooltip": "{count, plural, one [{count} character remaining] other [{count} characters remaining]}", + "remindMeLater": "Remind Me Later", "remove": "Remove", "removePasswordFail": "Failed to remove password", "removePasswordModalDescription": "Remove your current password for Session. Locally stored data will be re-encrypted with a randomly generated key, stored on your device.", "renew": "Renew", + "renewingPro": "Renewing Pro", "reply": "Reply", "requestRefund": "Request Refund", + "requestRefundPlatformWebsite": "Request a refund on the {platform} website, using the {platform_account} you signed up for Pro with.", "resend": "Resend", "resolving": "Loading country information...", "restart": "Restart", @@ -1100,6 +1166,9 @@ "undo": "Undo", "unknown": "Unknown", "unsupportedCpu": "Unsupported CPU", + "update": "Update", + "updateAccess": "Update Pro Access", + "updateAccessTwo": "Two ways to update your Pro access:", "updateApp": "App updates", "updateCommunityInformation": "Update Community Information", "updateCommunityInformationDescription": "Community name and description are visible to all community members", @@ -1114,8 +1183,6 @@ "updateGroupInformationEnterShorterDescription": "Please enter a shorter group description", "updateNewVersion": "A new version of Session is available, tap to update", "updateNewVersionDescription": "A new version ({version}) of Session is available.", - "updatePlan": "Update Plan", - "updatePlanTwo": "Two ways to update your plan:", "updateProfileInformation": "Update Profile Information", "updateProfileInformationDescription": "Your display name and display picture are visible in all conversations.", "updateReleaseNotes": "Go to Release Notes", @@ -1124,6 +1191,8 @@ "updated": "Last updated {relative_time} ago", "updates": "Updates", "updating": "Updating...", + "upgrade": "Upgrade", + "upgradeSession": "Upgrade Session", "upgradeTo": "Upgrade to", "uploading": "Uploading", "urlCopy": "Copy URL", @@ -1133,8 +1202,9 @@ "urlOpenDescriptionAlternative": "Links will open in your browser.", "usdNameShort": "USD", "useFastMode": "Use Fast Mode", - "viaStoreWebsite": "Via the {platform_store} website", - "viaStoreWebsiteDescription": "Change your plan using the {platform_account} you used to sign up with, via the {platform_store} website.", + "viaPlatformWebsiteDescription": "Change your plan using the {platform_account} you used to sign up with, via the {platform} website .", + "viaStoreWebsite": "Via the {platform} website", + "viaStoreWebsiteDescription": "Update your Pro access using the {platform_account} you used to sign up with, via the {platform_store} website.", "video": "Video", "videoErrorPlay": "Unable to play video.", "view": "View", @@ -1143,6 +1213,7 @@ "waitFewMinutes": "This can take a few minutes.", "waitOneMoment": "One moment please...", "warning": "Warning", + "warningIosVersionEndingSupport": "Support for iOS 15 has ended. Update to iOS 16 or later to continue receiving app updates.", "window": "Window", "yes": "Yes", "you": "You", diff --git a/_locales/et/messages.json b/_locales/et/messages.json index 97f6b4b7c..2f824e882 100644 --- a/_locales/et/messages.json +++ b/_locales/et/messages.json @@ -35,6 +35,7 @@ "adminRemovedUser": "{name} eemaldati administraatorina.", "adminRemovedUserMultiple": "{name} ja {count} teist eemaldati Administraatori kohalt.", "adminRemovedUserOther": "{name} ja {other_name} eemaldati Administraatori kohalt.", + "adminSendingPromotion": "{count, plural, one [Administraatori edutamine] other [Administraatori edutamine on saatmisel]}", "adminSettings": "Admini seaded", "adminTwoPromotedToAdmin": "{name} ja {other_name} määrati adminiks.", "andMore": "+{count}", @@ -267,14 +268,18 @@ "deleteAfterLegacyGroupsGroupUpdateErrorTitle": "Grupi uuendamine ebaõnnestus", "deleteAfterMessageDeletionStandardisationMessageDeletionForbidden": "Sul ei ole õiguseid teiste sõnumeid kustutada", "deleteMessage": "{count, plural, one [Kustuta sõnum] other [Kustuta sõnumid]}", + "deleteMessageConfirm": "{count, plural, one [Olete kindel, et soovite selle sõnumi kustutada?] other [Olete kindel, et soovite need sõnumid kustutada?]}", "deleteMessageDeleted": "{count, plural, one [Sõnum kustutatud] other [Sõnumid kustutatud]}", "deleteMessageDeletedGlobally": "See sõnum on kustutatud", "deleteMessageDeletedLocally": "See sõnum on sellest seadmest kustutatud", + "deleteMessageDescriptionDevice": "{count, plural, one [Kas olete kindel, et soovite selle sõnumi ainult sellest seadmest kustutada?] other [Kas olete kindel, et soovite need sõnumid ainult sellest seadmetest kustutada?]}", "deleteMessageDescriptionEveryone": "Kas soovite selle sõnumi kõigi jaoks kustutada?", "deleteMessageDeviceOnly": "Kustuta ainult sellest seadmest", "deleteMessageDevicesAll": "Kustuta kõigis minu seadmetes", "deleteMessageEveryone": "Kustuta kõigi jaoks", "deleteMessageFailed": "{count, plural, one [Sõnumi kustutamine ebaõnnestus] other [Sõnumite kustutamine ebaõnnestus]}", + "deleteMessageNoteToSelfWarning": "{count, plural, one [Seda sõnumit ei saa kõikidest teie seadmetest kustutada] other [Mõnda valitud sõnumit ei saa kõikidest teie seadmetest kustutada]}", + "deleteMessageWarning": "{count, plural, one [Seda sõnumit ei saa kõigi jaoks kustutada] other [Mõned sõnumid mille oled valinud ei saa kõigi jaoks kustutada]}", "deleteMessagesDescriptionEveryone": "Kas olete kindel, et soovite need sõnumid kõigi jaoks kustutada?", "deleting": "Kustutan", "developerToolsToggle": "Lülita arendusvahendid sisse/välja", @@ -376,6 +381,7 @@ "groupInviteFailedMultiple": "Ebaõnnestus kutsuda {name} ja {count} teisi kasutajaid gruppi {group_name}", "groupInviteFailedTwo": "Ebaõnnestus kutsuda {name} ja {other_name} gruppi {group_name}", "groupInviteFailedUser": "Ebaõnnestus kutsuda {name} gruppi {group_name}", + "groupInviteSending": "{count, plural, one [Saadan kutse] other [Saadan kutsed]}", "groupInviteSent": "Kutse saadetud", "groupInviteSuccessful": "Grupi kutse oli edukas", "groupInviteVersion": "Kasutajatel peab olema uusim versioon kutsete saamiseks", @@ -438,6 +444,8 @@ "incognitoKeyboardDescription": "Küsi inkognito režiimi, kui see on saadaval. Olenevalt klaviatuurist võib see taotlus eirata.", "info": "Info", "invalidShortcut": "Sobimatu otsetee", + "inviteFailed": "{count, plural, one [Kutse saatmine ebaõnnestus] other [Kutsete saatmine ebaõnnestus]}", + "inviteFailedDescription": "{count, plural, one [Kutset ei õnnestunud saata. Kas soovite uuesti proovida?] other [Kutseid ei õnnestunud saata. Kas soovite uuesti proovida?]}", "join": "Liitu", "later": "Hiljem", "learnMore": "Lisateave", @@ -497,6 +505,7 @@ "messageNewDescriptionDesktop": "Alustage uut vestlust, sisestades oma sõbra Account ID või ONS.", "messageNewDescriptionMobile": "Alustage uut vestlust, sisestades oma sõbra Account ID, ONS või skannides nende QR-koodi.", "messageNewYouveGot": "{count, plural, one [Sul on uus sõnum.] other [Sul on {count} uut sõnumit.]}", + "messageNewYouveGotGroup": "{count, plural, one [Sul on uus sõnum {group_name}.] other [Sul on {count} uut sõnumit {group_name}.]}", "messageReplyingTo": "Vastamine", "messageRequestGroupInvite": "{name} kutsus teid liituma grupiga {group_name}.", "messageRequestGroupInviteDescription": "Sõnumi saatmine sellele grupile aktsepteerib automaatselt grupikutse.", @@ -525,6 +534,7 @@ "messageVoiceSnippetGroup": "{author}: {emoji} Häälsõnum", "messages": "Sõnumid", "minimize": "Minimeeri", + "modalMessageCharacterDisplayDescription": "{count, plural, one [Sõnumitel on tähemärgipiirang {limit} tähemärki. Teil on {count} tähemärki alles.] other [Sõnumitel on tähemärgipiirang {limit} tähemärki. Teil on {count} tähemärki alles.]}", "networkName": "Session Network", "next": "Järgmine", "nicknameDescription": "Valige {name} jaoks hüüdnimi. See kuvatakse teile nii ühe-kahe kui ka grupivestlustes.", @@ -639,6 +649,7 @@ "pinUnpinConversation": "Vabasta vestlus", "preview": "Eelvaade", "pro": "Pro", + "proBadgesSent": "{count, plural, one [{total} Pro Tunnusmärk Saadetud] other [{total} Pro Tunnusmärki Saadetud]}", "profile": "Profiil", "profileDisplayPicture": "Kuvapilt", "profileDisplayPictureRemoveError": "Kuvapildi eemaldamine ebaõnnestus.", @@ -646,6 +657,8 @@ "profileDisplayPictureSizeError": "Palun valige väiksem fail.", "profileErrorUpdate": "Profiili uuendamine nurjus.", "promote": "Edenda", + "promotionFailed": "{count, plural, one [Edutamine ebaõnnestus] other [Edutamised ebaõnnestusid]}", + "promotionFailedDescription": "{count, plural, one [Edutamist ei õnnestunud rakendada. Kas soovite uuesti proovida?] other [Edutamisi ei õnnestunud rakendada. Kas soovite uuesti proovida?]}", "qrCode": "QR-kood", "qrNotAccountId": "See QR-kood ei sisalda kontotuvastusnumbrit", "qrNotRecoveryPassword": "See QR-kood ei sisalda taastamissõnumit", @@ -674,6 +687,7 @@ "recoveryPasswordRestoreDescription": "Sisestage oma taastamise parool, et laadida oma konto. Kui te ei ole seda salvestanud, leiate selle oma rakenduse seadetest.", "recoveryPasswordWarningSendDescription": "See on teie taastamislause. Kui saadate selle kellelegi, on tal täielik juurdepääs teie kontole.", "redo": "Tee uuesti", + "remainingCharactersTooltip": "{count, plural, one [{count} tähemärk alles] other [{count} tähemärki alles]}", "remove": "Eemalda", "removePasswordFail": "Salasõna eemaldamine ebaõnnestus", "reply": "Vasta", diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 1f236445a..9c9a505ef 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -64,6 +64,7 @@ "appNameStocks": "Bourse", "appNameWeather": "Météo", "appPro": "Session Pro", + "appProBadge": "Badge Session Pro", "appearanceAutoDarkMode": "Mode sombre automatique", "appearanceHideMenuBar": "Cacher la barre de menu", "appearanceLanguage": "Langue", @@ -159,6 +160,7 @@ "blockUnblockNameMultiple": "Êtes-vous sûr de vouloir débloquer {name} et {count} autres ?", "blockUnblockNameTwo": "Êtes-vous sûr de vouloir débloquer {name} et 1 autre ?", "blockUnblockedUser": "Débloqué {name}", + "blockedContactsManageDescription": "Afficher et gérer les contacts bloqués.", "call": "Appeler", "callsCalledYou": "{name} vous a appelé·e", "callsCannotStart": "Vous ne pouvez pas démarrer un nouvel appel. Terminez votre appel en cours d'abord.", @@ -183,6 +185,7 @@ "callsSettings": "Appels (Bêta)", "callsVoiceAndVideo": "Appels vocaux et vidéos", "callsVoiceAndVideoBeta": "Appels vocaux et vidéos (Beta)", + "callsVoiceAndVideoModalDescription": "Votre adresse IP est visible par votre interlocuteur et un serveur Session Foundation pendant que vous utilisez des appels bêta.", "callsVoiceAndVideoToggleDescription": "Active les appels vocaux et vidéo vers et depuis d'autres utilisateurs.", "callsYouCalled": "Vous avez appelé {name}", "callsYouMissedCallPermissions": "Vous avez manqué un appel de {name} car vous n'avez pas activé Appels vocaux et vidéo dans les Paramètres de confidentialité.", @@ -193,7 +196,9 @@ "cameraGrantAccessDescription": "Session a besoin de l’autorisation Caméra pour prendre des photos ou des vidéos, ou scanner des codes QR.", "cameraGrantAccessQr": "Session a besoin d'accéder à l'appareil photo pour scanner les codes QR", "cancel": "Annuler", + "change": "Modifier", "changePasswordFail": "Échec de changement de mot de passe", + "changePasswordModalDescription": "Changez votre mot de passe pour Session. Les données stockées localement seront re-chiffrées avec votre nouveau mot de passe.", "clear": "Effacer", "clearAll": "Effacer tout", "clearDataAll": "Effacer toutes les données", @@ -261,6 +266,7 @@ "contentDescriptionMessageComposition": "Rédaction d’un message", "contentDescriptionQuoteThumbnail": "Imagette du message cité", "contentDescriptionStartConversation": "Créer une conversation avec un nouveau contact", + "contentNotificationDescription": "Choisissez le contenu affiché dans les notifications locales lorsqu'un message entrant est reçu.", "conversationsAddToHome": "Ajouter à l’écran d’accueil", "conversationsAddedToHome": "Ajouté à l’écran d’accueil", "conversationsAudioMessages": "Messages audio", @@ -273,12 +279,18 @@ "conversationsDeleted": "Conversation supprimée", "conversationsEmpty": "Il n'y a aucun message dans {conversation_name}.", "conversationsEnter": "Entrer une clé", + "conversationsEnterDescription": "Définissez le fonctionnement des touches Entrée et Maj+Entrée dans les conversations.", + "conversationsEnterNewLine": "MAJUSCULE + ENTRÉE envoie un message, ENTRÉE commence une nouvelle ligne.", + "conversationsEnterSends": "ENTRÉE envoie un message, MAJ + ENTRÉE commence une nouvelle ligne.", "conversationsGroups": "Groupes", "conversationsMessageTrimming": "Élagage des messages", "conversationsMessageTrimmingTrimCommunities": "Purger les groupes", + "conversationsMessageTrimmingTrimCommunitiesDescription": "Supprimer automatiquement les messages de plus de 6 mois dans les communautés ayant plus de 2000 messages.", "conversationsNew": "Nouvelle conversation", "conversationsNone": "Vous n'avez pas encore de conversations", + "conversationsSendWithEnterKey": "Envoyer avec Entrée", "conversationsSendWithEnterKeyDescription": "Appuyer sur la touche Entrée enverra un message au lieu de commencer une nouvelle ligne.", + "conversationsSendWithShiftEnter": "Envoyer avec Maj+Entrée", "conversationsSettingsAllMedia": "Tous les médias", "conversationsSpellCheck": "Vérification orthographique", "conversationsSpellCheckDescription": "Activer le correcteur d'orthographe pour la saisie des messages.", @@ -287,7 +299,9 @@ "copy": "Copier", "create": "Créer", "creatingCall": "Création de l'appel", + "currentPassword": "Mot de passe actuel", "cut": "Couper", + "darkMode": "Mode sombre", "databaseErrorClearDataWarning": "Êtes-vous sûr de vouloir supprimer tous les messages, pièces jointes et données de compte de cet appareil et créer un nouveau compte ?", "databaseErrorGeneric": "Une erreur de base de données s'est produite.

Exportez les journaux de votre application pour les partager à des fins de dépannage. Si cela échoue, réinstallez Session et restaurez votre compte.", "databaseErrorRestoreDataWarning": "Êtes-vous sûr de vouloir supprimer tous les messages, pièces jointes et données de compte de cet appareil et restaurer votre compte depuis le réseau ?", @@ -365,6 +379,7 @@ "disappearingMessagesUpdated": "{admin_name} a mis à jour les paramètres des messages éphémères.", "disappearingMessagesUpdatedYou": "Vous avez mis à jour les paramètres des messages éphémères.", "dismiss": "Fermer", + "display": "Affichage", "displayNameDescription": "Cela peut être votre vrai nom, un alias ou autre — et vous pouvez le changer à tout moment.", "displayNameEnter": "Entrez votre nom d'affichage", "displayNameErrorDescription": "Veuillez choisir un nom d'utilisateur", @@ -402,10 +417,12 @@ "emojiReactsHoverYouNameTwoDesktop": "Vous et {name} avez réagi avec {emoji_name}", "emojiReactsNotification": "A réagi à votre message {emoji}", "enable": "Activer", + "enableNotifications": "Afficher les notifications lorsque vous recevez de nouveaux messages.", "enjoyingSession": "Vous aimez Session ?", "enjoyingSessionButtonNegative": "Des améliorations seraient utiles {emoji}", "enjoyingSessionButtonPositive": "C’est génial {emoji}", "enjoyingSessionDescription": "Vous utilisez Session depuis un petit moment, comment ça se passe ? Nous aimerions beaucoup connaître votre avis.", + "enter": "Entrer", "errorConnection": "Veuillez vérifier votre connexion internet et réessayer.", "errorCopyAndQuit": "Copier l'erreur et quitter", "errorDatabase": "Erreur de base de données", @@ -413,8 +430,11 @@ "errorUnknown": "Une erreur inconnue est survenue.", "failedToDownload": "Échec du téléchargement", "failures": "Échecs", + "feedback": "Donner votre avis", + "feedbackDescription": "Partagez votre expérience avec Session en répondant à un court sondage.", "file": "Fichier", "files": "Fichiers", + "followSystemSettings": "Faire correspondre aux paramètres systèmes.", "forever": "Définitivement", "from": "De :", "fullScreenToggle": "Activer/désactiver le plein écran", @@ -503,18 +523,24 @@ "groupUpdated": "Le groupe a été mis à jour", "handlingConnectionCandidates": "Traitement des candidats à la connexion", "helpFAQ": "FAQ", + "helpFAQDescription": "Consultez la FAQ de Session pour obtenir des réponses aux questions fréquentes.", "helpHelpUsTranslateSession": "Aidez-nous à traduire Session", + "helpReportABug": "Signaler un bug", "helpReportABugDescription": "Partagez quelques détails pour nous aider à résoudre votre problème. Exportez vos journaux, puis téléchargez le fichier via le centre d'assistance de Session.", "helpReportABugExportLogs": "Exporter les journaux", "helpReportABugExportLogsDescription": "Exportez vos journaux, puis télécharger le fichier par le service d'aide de Session.", "helpReportABugExportLogsSaveToDesktop": "Enregistrer sur le bureau", + "helpReportABugExportLogsSaveToDesktopDescription": "Enregistrez ce fichier, puis partagez-le avec les développeurs de Session.", "helpSupport": "Assistance", + "helpTranslateSessionDescription": "Aidez à traduire Session en plus de 80 langues !", "helpWedLoveYourFeedback": "Nous aimerions avoir votre avis", "hide": "Masquer", + "hideMenuBarDescription": "Activer/désactiver la visibilité de la barre de menu système.", "hideNoteToSelfDescription": "Êtes-vous sûr de vouloir masquer Note pour soi-même de votre liste de conversations ?", "hideOthers": "Cacher les autres", "image": "Image", "images": "images", + "important": "Important", "incognitoKeyboard": "Clavier incognito", "incognitoKeyboardDescription": "Demander le mode incognito, si disponible. Selon le clavier que vous utilisez, votre clavier peut ignorer cette demande.", "info": "Info", @@ -523,6 +549,9 @@ "inviteFailedDescription": "{count, plural, one [L'invitation n'a pas pu être envoyée. Voulez-vous réessayer ?] other [Les invitations n'ont pas pu être envoyées. Voulez-vous réessayer ?]}", "join": "Rejoindre", "later": "Plus tard", + "launchOnStartDescriptionDesktop": "Lancer automatiquement Session au démarrage de votre ordinateur.", + "launchOnStartDesktop": "Lancer au démarrage", + "launchOnStartupDisabledDesktop": "Ce paramètre est géré par votre système sous Linux. Pour activer le démarrage automatique, ajoutez Session à vos applications de démarrage dans les paramètres système.", "learnMore": "En savoir plus", "leave": "Quitter", "leaving": "Départ...", @@ -547,6 +576,7 @@ "linkPreviewsSendModalDescription": "Vous n'aurez pas une protection complète des métadonnées en envoyant des aperçus de liens.", "linkPreviewsTurnedOff": "Aperçus des liens désactivés", "linkPreviewsTurnedOffDescription": "Session doit contacter des sites web liés pour générer les aperçus des liens que vous envoyez et recevez.

Vous pouvez les activer dans les paramètres de Session.", + "links": "Liens", "loadAccount": "Charger le compte", "loadAccountProgressMessage": "Chargement de votre compte", "loading": "Chargement...", @@ -559,7 +589,9 @@ "lockAppStatus": "État de verrouillage", "lockAppUnlock": "Appuyez pour déverrouiller", "lockAppUnlocked": "Session est déverrouillé", + "logs": "Journaux", "manageMembers": "Gérer Membres", + "managePro": "Gérer Pro", "max": "Maximale", "media": "Médias", "members": "{count, plural, one [{count} membre] other [{count} membres]}", @@ -573,8 +605,10 @@ "membersInviteShareMessageHistory": "Partager l'historique des messages", "membersInviteShareNewMessagesOnly": "Partager seulement les nouveaux messages", "membersInviteTitle": "Inviter", + "menuBar": "Barre de menu", "message": "Message", "messageBubbleReadMore": "Lire plus", + "messageCopy": "Copier le message", "messageEmpty": "Ce message est vide.", "messageErrorDelivery": "La transmission du message a échoué", "messageErrorLimit": "La taille maximale de message est atteinte", @@ -626,7 +660,9 @@ "modalMessageTooLongDescription": "Veuillez raccourcir votre message a {limit} caractères ou moins.", "modalMessageTooLongTitle": "Le message est trop long", "networkName": "Session Network", + "newPassword": "Nouveau mot de passe", "next": "Suivant", + "nextSteps": "Étapes suivantes", "nicknameDescription": "Choisissez un pseudo pour {name}. Il apparaîtra dans vos conversations individuelles et de groupe.", "nicknameEnter": "Saisissez un surnom", "nicknameErrorShorter": "Veuillez choisir un surnom plus court", @@ -640,6 +676,9 @@ "noteToSelfEmpty": "Vous n'avez aucun message dans Note à mon intention.", "noteToSelfHide": "Cacher la Note à soi-même", "noteToSelfHideDescription": "Êtes-vous sûr de vouloir masquer Note à moi-même ?", + "notificationDisplay": "Affichage des notifications", + "notificationSenderNameAndPreview": "Afficher le nom de l'expéditeur et un aperçu du contenu du message.", + "notificationSenderNameOnly": "Afficher uniquement le nom de l'expéditeur sans aucun contenu du message.", "notificationsAllMessages": "Tous les messages", "notificationsContent": "Contenu de la notification", "notificationsContentDescription": "Les informations affichées dans les notifications.", @@ -650,6 +689,7 @@ "notificationsFastModeDescription": "Vous serez averti de nouveaux messages de manière fiable et immédiate en utilisant les serveurs de notification de Google.", "notificationsFastModeDescriptionHuawei": "Les serveurs de notification Huawei vous informent immédiatement et de manière fiable de l'arrivée de nouveaux messages.", "notificationsFastModeDescriptionIos": "Vous serez averti de nouveaux messages de manière fiable et immédiate en utilisant les serveurs de notification d'Apple.", + "notificationsGenericOnly": "Afficher une notification générique de Session sans le nom de l'expéditeur ni le contenu du message.", "notificationsGoToDevice": "Accédez aux paramètres de notifications de l'appareil", "notificationsHeaderAllMessages": "Notifications - Toutes", "notificationsHeaderMentionsOnly": "Notifications - Mentions seulement", @@ -657,6 +697,7 @@ "notificationsIosGroup": "{name} à {conversation_name}", "notificationsIosRestart": "Vous avez peut-être reçu des messages alors que votre {device} redémarrait.", "notificationsLedColor": "Couleur de la DEL", + "notificationsMakeSound": "Jouer un son lorsque vous recevez de nouveaux messages.", "notificationsMentionsOnly": "Mentions seulement", "notificationsMessage": "Notifications de message", "notificationsMostRecent": "Le plus récent de : {name}", @@ -678,6 +719,7 @@ "off": "Désactivé", "okay": "Okay", "on": "Activé", + "onDevice": "Sur votre appareil {device_type}", "onboardingAccountCreate": "Créer un compte", "onboardingAccountCreated": "Compte Créé", "onboardingAccountExists": "J'ai un compte", @@ -705,8 +747,10 @@ "openSurvey": "Ouvrir le questionnaire", "other": "Autre", "oxenFoundation": "Oxen Foundation", + "password": "Mot de passe", "passwordChange": "Changer le mot de passe", "passwordChangeShortDescription": "Obligation de changer le mot de passe pour déverrouiller Session.", + "passwordChangedDescriptionToast": "Votre mot de passe a été changé. Veuillez le conserver en sécurité.", "passwordConfirm": "Confirmez le mot de passe", "passwordCreate": "Créer un mot de passe", "passwordCurrentIncorrect": "Votre mot de passe actuel est incorrect.", @@ -718,8 +762,21 @@ "passwordErrorMatch": "Les mots de passe ne correspondent pas.", "passwordFailed": "Échec de la définition du mot de passe", "passwordIncorrect": "Mot de passe incorrect", + "passwordNewConfirm": "Confirmer le nouveau mot de passe", "passwordRemove": "Supprimer le mot de passe", + "passwordRemoveShortDescription": "Retirer le mot de passe requis pour déverrouiller Session", + "passwordRemovedDescriptionToast": "Votre mot de passe a été supprimé.", "passwordSet": "Définir un mot de passe", + "passwordSetDescriptionToast": "Votre mot de passe a été défini. Veuillez le conserver en sécurité.", + "passwordSetShortDescription": "Mot de passe requis pour déverrouiller Session au démarrage.", + "passwordStrengthCharLength": "Plus de 12 caractères", + "passwordStrengthIncludeNumber": "Inclut un chiffre", + "passwordStrengthIncludesLowercase": "Comprend une lettre minuscule", + "passwordStrengthIncludesSymbol": "Inclut un symbole", + "passwordStrengthIncludesUppercase": "Contient une lettre majuscule", + "passwordStrengthIndicator": "Indicateur de robustesse du mot de passe", + "passwordStrengthIndicatorDescription": "Définir un mot de passe robuste permet de protéger vos messages et pièces jointes en cas de perte ou de vol de votre appareil.", + "passwords": "Mots de passe", "paste": "Coller", "permissionChange": "Changement de permission", "permissionMusicAudioDenied": "Session a besoin d'un accès à la musique et à l'audio pour envoyer des fichiers, de la musique et de l'audio, mais il a été refusé définitivement. Appuyez sur Paramètres → Autorisations, puis activez \"Musique et audio\".", @@ -759,18 +816,40 @@ "pinConversation": "Épingler la conversation", "pinUnpin": "Désépingler", "pinUnpinConversation": "Désépingler la conversation", + "plusLoadsMore": "Plus de téléchargement...", + "plusLoadsMoreDescription": "Nouvelles fonctionnalités Pro à venir. Découvrez ce qui vous attend dans la feuille de route Pro {icon}", + "preferences": "Préférences", "preview": "Aperçu", + "previewNotification": "Aperçu de la notification", "pro": "Pro", "proActivated": "Activé", + "proAllSet": "Tout est prêt !", "proAlreadyPurchased": "Vous avez déjà", "proAnimatedDisplayPicture": "Téléchargez des GIF et des images WebP animées pour votre photo de profil !", "proAnimatedDisplayPictureCallToActionDescription": "Obtenez des photos de profil animées et débloquez des fonctionnalités premium avec Session Pro", "proAnimatedDisplayPictureFeature": "Photo de profil animée", "proAnimatedDisplayPictureModalDescription": "les utilisateurs peuvent télécharger des GIFs", + "proAnimatedDisplayPictures": "Photos de profil animées", + "proAnimatedDisplayPicturesDescription": "Définissez des GIF et des images WebP animées comme photo de profil.", "proAnimatedDisplayPicturesNonProModalDescription": "Téléversez des GIF avec", + "proAutoRenewTime": "Pro se renouvelle automatiquement dans {time}", + "proBadge": "Badge Pro", + "proBadgeVisible": "Afficher l’insigne Session Pro aux autres utilisateurs", + "proBadges": "Badges", + "proBadgesDescription": "Affichez votre soutien à Session avec un badge exclusif, à côté de votre nom d'affichage.", + "proBadgesSent": "{count, plural, one [Badge {total} Pro envoyé] other [Badge {total} Pro envoyé]}", + "proBetaFeatures": "Fonctionnalités Pro", + "proBilledAnnually": "{price} facturé annuellement", + "proBilledMonthly": "{price} facturé mensuellement", + "proBilledQuarterly": "{price} facturé trimestriellement", "proCallToActionLongerMessages": "Vous voulez envoyer des messages plus longs ? Envoyez plus de messages et débloqué les fonctionnalités premium avec Session Pro", "proCallToActionPinnedConversations": "Vous voulez plus de messages épinglés ? Organisez vos chats et débloquez les fonctionnalités premium avec Session Pro", "proCallToActionPinnedConversationsMoreThan": "Vous voulez plus que 5 messages épinglés ? Organisez vos chats et débloquez les fonctionnalités premium avec Session Pro", + "proExpired": "Expiré", + "proExpiringSoon": "Expiration imminente", + "proExpiringTime": "Pro expire dans {time}", + "proFaq": "FAQ Pro", + "proFaqDescription": "Trouvez des réponses aux questions fréquentes dans la FAQ de Session Pro.", "proFeatureListAnimatedDisplayPicture": "Téléversez des photos de profil au format GIF ou WebP", "proFeatureListLargerGroups": "Groupes de discussions plus larges, jusqu'à 300 participants", "proFeatureListLoadsMore": "Plus offre des fonctions exclusives additionnelles", @@ -778,10 +857,31 @@ "proFeatureListPinnedConversations": "Épinglez un nombre illimité de conversations", "proGroupActivated": "Groupe activé", "proGroupActivatedDescription": "Ce groupe a une capacité étendue ! Il peut contenir jusqu’à 300 membres grâce à un administrateur qui dispose de", + "proGroupsUpgraded": "{count, plural, one [{total} groupe mis à niveau] other [{total} groupes mis à niveau]}", "proIncreasedAttachmentSizeFeature": "Taille de pièce jointe augmentée", "proIncreasedMessageLengthFeature": "Longueur de message augmentée", + "proLargerGroups": "Groupes plus grands", + "proLargerGroupsDescription": "Les groupes dont vous êtes administrateur sont automatiquement mis à niveau pour prendre en charge 300 membres.", + "proLongerMessages": "Messages plus longs", + "proLongerMessagesDescription": "Vous pouvez envoyer des messages jusqu'à 10000 caractères dans toutes les conversations.", + "proLongerMessagesSent": "{count, plural, one [Message plus long {total} envoyé] other [{total} Messages plus longs envoyés]}", "proMessageInfoFeatures": "Ce message utilise les fonctionnalités suivantes de Session Pro :", + "proPercentOff": "{percent}% de réduction", + "proPinnedConversations": "{count, plural, one [{total} Conversation épinglée] other [{total} Conversations épinglées]}", + "proPriceOneMonth": "1 mois – {monthly_price} / mois", + "proPriceThreeMonths": "3 mois - {monthly_price} / mois", + "proPriceTwelveMonths": "12 mois - {monthly_price} / mois", + "proRefundDescription": "Nous sommes désolés de vous voir partir. Voici ce que vous devez savoir avant de demander un remboursement.", + "proRefundRequestSessionSupport": "Votre demande de remboursement sera traitée par le service de Session.

Demandez un remboursement en appuyant sur le bouton ci-dessous et en remplissant le formulaire de demande de remboursement.

Bien que le service de Session fait au mieux afin de traiter les demandes de remboursement dans un délai de 24-72 heures, le traitement peut être plus long en période de forte demande.", + "proRefunding": "Remboursement de Pro", + "proRequestedRefund": "Remboursement demandé", "proSendMore": "Envoyez plus avec", + "proSettings": "Paramètres Pro", + "proStats": "Vos statistiques Pro", + "proStatsTooltip": "Les statistiques Pro reflètent l'utilisation sur cet appareil et peuvent apparaître différemment sur les appareils connectés", + "proTosPrivacy": "En mettant à jour, vous acceptez les Conditions d'utilisation {icon} et la Politique de confidentialité {icon} de Session Pro", + "proUnlimitedPins": "Épingles illimitées", + "proUnlimitedPinsDescription": "Organisez toutes vos discussions avec un nombre illimité de conversations épinglées.", "proUserProfileModalCallToAction": "Vous voulez tirer le meilleur parti de Session ? Passez à Session Pro pour une expérience de messagerie améliorée.", "profile": "Profil", "profileDisplayPicture": "Définir une photo de profil", @@ -813,6 +913,7 @@ "recommended": "Recommandé", "recoveryPasswordBannerDescription": "Enregistrez votre mot de passe de récupération pour vous assurer de ne pas perdre l'accès à votre compte.", "recoveryPasswordBannerTitle": "Enregistrer votre mot de passe de récupération", + "recoveryPasswordDescription": "Utilisez votre mot de passe de récupération pour charger votre compte sur de nouveaux appareils.

Votre compte ne peut pas être récupéré sans votre mot de passe de récupération. Assurez-vous qu'il soit stocké en lieu sûr et sécurisé ;— et ne le partagez avec personne.", "recoveryPasswordEnter": "Entrez votre mot de passe de récupération", "recoveryPasswordErrorLoad": "Une erreur s'est produite lors du chargement de votre mot de passe de récupération.

Veuillez exporter vos journaux, puis téléversez le fichier via l'assistance de Session pour aider à résoudre ce problème.", "recoveryPasswordErrorMessageGeneric": "Veuillez vérifier votre mot de passe de récupération et réessayer.", @@ -826,6 +927,8 @@ "recoveryPasswordHideRecoveryPassword": "Cacher le mot de passe de récupération", "recoveryPasswordHideRecoveryPasswordDescription": "Masquer définitivement votre mot de passe de récupération sur cet appareil.", "recoveryPasswordRestoreDescription": "Entrez votre mot de passe de récupération pour charger votre compte. Si vous ne l'avez pas enregistré, vous pouvez le trouver dans les paramètres de l'application.", + "recoveryPasswordView": "Afficher le mot de passe de récupération", + "recoveryPasswordVisibility": "Visibilité du mot de passe de récupération", "recoveryPasswordWarningSendDescription": "Voici votre mot de passe de récupération. Si vous l'envoyez à quelqu'un, il aura un accès complet à votre compte.", "recreateGroup": "Recréer le groupe", "redo": "Rétablir", @@ -833,7 +936,10 @@ "remainingCharactersTooltip": "{count, plural, one [{count} caractères restants] other [{count} caractères restants]}", "remove": "Supprimer", "removePasswordFail": "Échec de supprimer le mot de passe", + "removePasswordModalDescription": "Supprimez votre mot de passe actuel pour Session. Les données stockées localement seront à nouveau chiffrées à l'aide d'une clé générée aléatoirement, stockée sur votre appareil.", + "renew": "Renouveler", "reply": "Répondre", + "requestRefund": "Demander un remboursement", "resend": "Renvoyer", "resolving": "Chargement des pays…", "restart": "Redémarrer", @@ -849,6 +955,8 @@ "screenSecurity": "Sécurité d'écran", "screenshotNotifications": "Notifications de capture d'écran", "screenshotNotificationsDescription": "Recevoir une notification lorsqu'un contact fait une capture d'écran d'une conversation individuelle.", + "screenshotProtectionDescriptionDesktop": "Masquer la fenêtre de Session dans les captures d’écran prises sur cet appareil.", + "screenshotProtectionDesktop": "Protection contre les captures d’écran", "screenshotTaken": "{name} a pris une capture d'écran.", "search": "Recherche", "searchContacts": "Chercher parmi les contacts", @@ -889,12 +997,16 @@ "sessionNotifications": "Notifications", "sessionPermissions": "Permissions", "sessionPrivacy": "Confidentialité", + "sessionProBeta": "Session Pro Bêta", "sessionRecoveryPassword": "Mot de passe de récupération", "sessionSettings": "Paramètres", "set": "Appliquer", "setCommunityDisplayPicture": "Définir la photo de la communauté", + "setPasswordModalDescription": "Définissez un mot de passe pour Session. Les données stockées localement seront chiffrées avec ce mot de passe. Il vous sera demandé de saisir ce mot de passe chaque fois que Session sera lancé.", + "settingsCannotChangeDesktop": "Impossible de mettre à jour les paramètres", "settingsRestartDescription": "Vous devez redémarrer Session pour appliquer ces changements.", "settingsScreenSecurityDesktop": "Sécurité d'écran", + "settingsStartCategoryDesktop": "Démarrage", "share": "Partager", "shareAccountIdDescription": "Invitez votre ami à discuter avec vous sur Session en partageant votre ID de compte avec lui.", "shareAccountIdDescriptionCopied": "Partagez avec vos amis où vous communiquez habituellement avec eux - puis déplacez la conversation ici.", @@ -906,18 +1018,25 @@ "showLess": "Afficher moins", "showNoteToSelf": "Afficher la note pour soi-même", "showNoteToSelfDescription": "Êtes-vous sûr de vouloir afficher Note pour soi-même dans votre liste de conversations ?", + "spellChecker": "Correcteur d'orthographe", "stakingRewardPool": "Staking Reward Pool", "stickers": "Autocollants", + "strength": "Solidité", + "supportDescription": "Vous avez des problèmes ? Explorez des articles d'aide ou ouvrez un ticket avec le support Session.", "supportGoTo": "Accéder à la page d’assistance", "systemInformationDesktop": "Information système : {information}", "tapToRetry": "Tapez pour réessayer", "theContinue": "Continuer", "theDefault": "Valeur par défaut", "theError": "Erreur", + "theReturn": "Retour", + "themePreview": "Aperçu du thème", "tokenNameLong": "Session Token", "tokenNameShort": "SESH", "tooltipAccountIdVisible": "L’ID de compte de {name} est visible en fonction de vos interactions précédentes", "tooltipBlindedIdCommunities": "Les ID aveuglés sont utilisés dans les communautés pour réduire le spam et renforcer la confidentialité", + "translate": "Traduction", + "tray": "Barre de tâches", "tryAgain": "Réessayer", "typingIndicators": "Indicateurs de saisie", "typingIndicatorsDescription": "Voir et partager les indicateurs de saisie.", @@ -938,16 +1057,21 @@ "updateGroupInformationEnterShorterDescription": "Veuillez entrer une description de groupe plus courte", "updateNewVersion": "Une nouvelle version de Session est disponible, appuyez pour lancer la mise à jour", "updateNewVersionDescription": "Une nouvelle version ({version}) de Session est disponible.", + "updateProfileInformation": "Mettre à jour les informations du profil", + "updateProfileInformationDescription": "Votre nom d'affichage et votre photo de profil sont visibles dans toutes les conversations.", "updateReleaseNotes": "Accéder aux notes de mise à jour", "updateSession": "Mise à jour de Session", "updateVersion": "Version {version}", "updated": "Dernière mise à jour il y a {relative_time}", + "updates": "Mises à jour", + "updating": "Mise à jour...", "upgradeTo": "Mettre à niveau à", "uploading": "Téléversement", "urlCopy": "Copier l'adresse URL", "urlOpen": "Ouvrir l'URL", "urlOpenBrowser": "Ceci s'ouvrira dans votre navigateur.", "urlOpenDescription": "Êtes-vous sûr de vouloir ouvrir cette adresse URL dans votre navigateur web ?

{url}", + "urlOpenDescriptionAlternative": "Les liens s'ouvriront dans votre navigateur.", "usdNameShort": "USD", "useFastMode": "Utiliser le mode rapide", "video": "Vidéo", @@ -960,6 +1084,9 @@ "warning": "Attention", "window": "Fenêtre", "yes": "Oui", - "you": "Vous" + "you": "Vous", + "yourRecoveryPassword": "Votre mot de passe de récupération", + "zoomFactor": "Niveau de Zoom", + "zoomFactorDescription": "Ajuster la taille du texte et des éléments visuels." } diff --git a/_locales/nb/messages.json b/_locales/nb/messages.json index 5b88411b3..cc37711b0 100644 --- a/_locales/nb/messages.json +++ b/_locales/nb/messages.json @@ -438,6 +438,7 @@ "helpReportABugExportLogs": "Eksportlogger", "helpReportABugExportLogsDescription": "Eksporter logger, deretter last opp filen gjennom Session's Help Desk.", "helpReportABugExportLogsSaveToDesktop": "Lagre til skrivebordet", + "helpReportABugExportLogsSaveToDesktopDescription": "Lagre denne filen, så del den med Session-utviklerne.", "helpSupport": "Brukerstøtte", "helpWedLoveYourFeedback": "Vi ønsker gjerne dine tilbakemeldinger", "hide": "Skjul", @@ -683,6 +684,7 @@ "recoveryPasswordBannerDescription": "Lagre ditt gjenopprettingspassord for å sikre at du ikke mister tilgang til kontoen din.", "recoveryPasswordBannerTitle": "Lagre ditt gjenopprettingspassord", "recoveryPasswordEnter": "Angi ditt gjenopprettingspassord", + "recoveryPasswordErrorLoad": "En feil oppstod når ditt gjenopprettingspassord forsøkte å laste.

Vennligst eksporter loggene dine, så opplast filen gjennom Hjelpesenteret til Session.", "recoveryPasswordErrorMessageGeneric": "Sjekk gjenopprettingspassordet ditt og prøv igjen.", "recoveryPasswordErrorMessageIncorrect": "Noen av ordene i gjenopprettingspassordet ditt er feil. Vennligst sjekk og prøv igjen.", "recoveryPasswordErrorMessageShort": "Gjenopprettingspassordet du oppga er ikke langt nok. Vennligst sjekk og prøv igjen.", diff --git a/_locales/nl/messages.json b/_locales/nl/messages.json index f7c96909a..bcdd7383f 100644 --- a/_locales/nl/messages.json +++ b/_locales/nl/messages.json @@ -196,7 +196,6 @@ "cameraGrantAccessDescription": "Session heeft toegang tot de camera nodig om foto's en video's te maken of QR-codes te scannen.", "cameraGrantAccessQr": "Session heeft toegang tot de camera nodig om QR-codes te scannen", "cancel": "Annuleren", - "cancelPlan": "Abonnement annuleren", "change": "Wijzigen", "changePasswordFail": "Wachtwoord wijzigen mislukt", "changePasswordModalDescription": "Wijzig je wachtwoord voor Session. Lokaal opgeslagen gegevens worden opnieuw versleuteld met je nieuwe wachtwoord.", @@ -301,7 +300,6 @@ "create": "Aanmaken", "creatingCall": "Oproep starten", "currentPassword": "Huidig wachtwoord", - "currentPlan": "Huidig abonnement", "cut": "Knippen", "darkMode": "Donkere modus", "databaseErrorClearDataWarning": "Weet u zeker dat u alle berichten, bijlagen en accountgegevens van dit apparaat wilt verwijderen en een nieuw account wilt aanmaken?", @@ -719,7 +717,6 @@ "okay": "Oké", "on": "Aan", "onDevice": "Op je {device_type} apparaat", - "onDeviceDescription": "Open dit Session account op een {device_type} apparaat waarop je bent aangemeld met het {platform_account} waarmee je je oorspronkelijk hebt geregistreerd. Wijzig vervolgens je abonnement via de instellingen van Session Pro.", "onboardingAccountCreate": "Account aanmaken", "onboardingAccountCreated": "Account aangemaakt", "onboardingAccountExists": "Ik heb een account", @@ -744,7 +741,6 @@ "onsErrorNotRecognized": "We konden deze ONS niet herkennen. Controleer deze alsjeblieft en probeer het opnieuw.", "onsErrorUnableToSearch": "We konden niet zoeken naar deze ONS. Probeer het later opnieuw.", "open": "Openen", - "openStoreWebsite": "Open de {platform_store} website", "openSurvey": "Enquête openen", "other": "Overige", "oxenFoundation": "Oxen Foundation", @@ -825,7 +821,6 @@ "pro": "Pro", "proActivated": "Geactiveerd", "proAllSet": "Alles is geregeld!", - "proAllSetDescription": "Je Session Pro abonnement is bijgewerkt! Je wordt gefactureerd wanneer je huidige Pro abonnement automatisch wordt verlengd op {date}.", "proAlreadyPurchased": "Je hebt al", "proAnimatedDisplayPicture": "Upload nu GIF's en geanimeerde WebP-afbeeldingen voor je profielfoto!", "proAnimatedDisplayPictureCallToActionDescription": "Krijg geanimeerde profielfoto's en ontgrendel premiumfuncties met Session Pro", @@ -839,28 +834,24 @@ "proBadgeVisible": "Toon het Session Pro badge aan andere gebruikers", "proBadges": "Badges", "proBadgesDescription": "Toon je steun voor Session met een exclusieve badge naast je schermnaam.", + "proBetaFeatures": "Pro functies", "proBilledAnnually": "{price} Jaarlijks gefactureerd", "proBilledMonthly": "{price} Maandelijks gefactureerd", "proBilledQuarterly": "{price} per kwartaal gefactureerd", "proCallToActionLongerMessages": "Wil je langere berichten versturen? Verstuur meer tekst en ontgrendel premiumfuncties met Session Pro", "proCallToActionPinnedConversations": "Wil je meer vastzetten? Organiseer je chats en ontgrendel premiumfuncties met Session Pro", "proCallToActionPinnedConversationsMoreThan": "Wil je meer dan 5 vastgezette gesprekken? Organiseer je chats en ontgrendel premiumfuncties met Session Pro", - "proDiscountTooltip": "Je huidige abonnement is al met {percent}% korting ten opzichte van de volledige Session Pro prijs.", "proExpired": "Verlopen", - "proExpiredDescription": "Helaas is je Pro abonnement verlopen. Verleng om toegang te blijven houden tot de exclusieve voordelen en functies van Session Pro.", "proExpiringSoon": "Verloopt binnenkort", - "proExpiringSoonDescription": "Je Pro abonnement verloopt over {time}. Werk je abonnement bij om toegang te blijven houden tot de exclusieve voordelen en functies van Session Pro.", "proFaq": "Pro FAQ", - "proFaqDescription": "Vind antwoorden op veelgestelde vragen in de Session FAQ.", + "proFaqDescription": "Vind antwoorden op veelgestelde vragen in de Session Pro FAQ.", "proFeatureListAnimatedDisplayPicture": "Upload GIF- en WebP-profielfoto's", "proFeatureListLargerGroups": "Groepsgesprekken tot wel 300 leden", "proFeatureListLoadsMore": "En nog veel meer exclusieve functies", "proFeatureListLongerMessages": "Berichten tot 10.000 tekens", "proFeatureListPinnedConversations": "Onbeperkt gesprekken vastzetten", - "proFeatures": "Pro functies", "proGroupActivated": "Groep geactiveerd", "proGroupActivatedDescription": "Deze groep heeft een grotere capaciteit! Er kunnen nu tot 300 leden deelnemen omdat een groepsbeheerder een", - "proImportantDescription": "Het aanvragen van een terugbetaling is definitief. Indien goedgekeurd, wordt je Pro abonnement onmiddellijk geannuleerd en verlies je de toegang tot alle Pro functies.", "proIncreasedAttachmentSizeFeature": "Verhoogde bijlagegrootte", "proIncreasedMessageLengthFeature": "Verlengde berichtlengte", "proLargerGroups": "Grotere groepen", @@ -869,46 +860,21 @@ "proLongerMessagesDescription": "Je kunt berichten tot 10.000 tekens verzenden in alle gesprekken.", "proMessageInfoFeatures": "Dit bericht maakte gebruik van de volgende Session Pro-functies:", "proPercentOff": "{percent}% korting", - "proPlanActivatedAuto": "Je Session Pro abonnement is actief!

Je abonnement wordt automatisch verlengd voor een nieuw {current_plan} op {date}. Wijzigingen aan je abonnement gaan in wanneer Pro de volgende keer wordt verlengd.", - "proPlanActivatedAutoShort": "Je Session Pro abonnement is actief!

Je abonnement wordt automatisch verlengd met een {current_plan} op {date}.", - "proPlanActivatedNotAuto": "Je Session Pro abonnement verloopt op {date}.

Werk je abonnement nu bij om ononderbroken toegang te behouden tot exclusieve Pro functies.", - "proPlanExpireDate": "Je Session Pro abonnement verloopt op {date}.", - "proPlanNotFound": "Pro abonnement niet gevonden", - "proPlanNotFoundDescription": "Er is geen actief abonnement gevonden voor je account. Als je denkt dat dit een vergissing is, neem dan contact op met de ondersteuning van Session voor hulp.", - "proPlanPlatformRefund": "Omdat je je oorspronkelijk hebt aangemeld voor Session Pro via de {platform_store} winkel, moet je hetzelfde {platform_account} gebruiken om een terugbetaling aan te vragen.", - "proPlanPlatformRefundLong": "Omdat je je oorspronkelijk hebt aangemeld voor Session Pro via de {platform_store} winkel, wordt je restitutieverzoek afgehandeld door Session Support.

Vraag een restitutie aan door op de knop hieronder te drukken en het restitutieformulier in te vullen.

Hoewel Session Support ernaar streeft om restitutieverzoeken binnen 24-72 uur te verwerken, kan het tijdens drukte langer duren", - "proPlanRecover": "Pro abonnement herstellen", - "proPlanRenew": "Pro abonnement verlengen", - "proPlanRenewDesktop": "Momenteel kunnen Pro abonnementen alleen worden gekocht en verlengd via de {platform_store}- of {platform_store} winkels. Omdat je Session Desktop gebruikt, kun je je abonnement hier niet verlengen.

De ontwikkelaars van Session Pro werken hard aan alternatieve betaalmogelijkheden, zodat gebruikers Pro abonnementen buiten de {platform_store}- en {platform_store} winkels kunnen aanschaffen. Pro Routekaart", - "proPlanRenewDesktopLinked": "Verleng je abonnement in de Session Pro instellingen op een gekoppeld apparaat met Session geïnstalleerd via de {platform_store} of {platform_store} winkel.", - "proPlanRenewDesktopStore": "Verleng je abonnement op de {platform_store} website met het {platform_account} waarmee je je voor Pro hebt aangemeld.", - "proPlanRenewSupport": "Je Session Pro abonnement is verlengd! Bedankt voor je steun aan de Session Network.", - "proPlanRestored": "Pro abonnement hersteld", - "proPlanRestoredDescription": "Een geldig abonnement voor Session Pro is gedetecteerd en je Pro status is hersteld!", - "proPlanSignUp": "Omdat je je oorspronkelijk hebt aangemeld voor Session Pro via de {platform_store} winkel, moet je je {platform_account} gebruiken om je abonnement bij te werken.", "proPriceOneMonth": "1 maand - {monthly_price} / maand", "proPriceThreeMonths": "3 maanden - {monthly_price} / maand", "proPriceTwelveMonths": "12 maanden - {monthly_price} / maand", "proRefundDescription": "Het spijt ons dat je vertrekt. Dit moet je weten voordat je een terugbetaling aanvraagt.", - "proRefundNextSteps": "{platform_account} verwerkt nu je terugbetalingsverzoek. Dit duurt meestal 24-48 uur. Afhankelijk van hun beslissing kan je Pro status wijzigen in Session.", "proRefundRequestSessionSupport": "Je restitutieverzoek wordt afgehandeld door Session Support.

Vraag een restitutie aan door op de knop hieronder te drukken en het restitutieformulier in te vullen.

Hoewel Session Support ernaar streeft om restitutieverzoeken binnen 24-72 uur te verwerken, kan het tijdens drukte langer duren.", - "proRefundRequestStorePolicies": "Je terugbetalingsverzoek wordt uitsluitend afgehandeld door {platform_account} via de website van {platform_account}.

Vanwege het restitutiebeleid van {platform_account} hebben ontwikkelaars van Session geen invloed op de uitkomst van terugbetalingsverzoeken. Dit geldt zowel voor de goedkeuring of afwijzing van het verzoek als voor het wel of niet toekennen van een volledige of gedeeltelijke terugbetaling.", - "proRefundSupport": "Neem contact op met {platform_account} voor verdere updates over je restitutieverzoek. Vanwege het restitutiebeleid van {platform_account} hebben de ontwikkelaars van Session geen invloed op de uitkomst van restitutieverzoeken.

{platform_store} Terugbetalingsondersteuning", "proRefunding": "Terugbetalen Pro", - "proRefundingDescription": "Terugbetalingen voor Session Pro abonnementen worden uitsluitend afgehandeld door {platform_account} via de {platform_store} Store.

Vanwege het restitutiebeleid van {platform_account} hebben ontwikkelaars van Session geen invloed op de uitkomst van terugbetalingsverzoeken. Dit geldt zowel voor de goedkeuring of afwijzing van het verzoek als voor het wel of niet toekennen van een volledige of gedeeltelijke terugbetaling.", "proRequestedRefund": "Terugbetaling aangevraagd", "proSendMore": "Verstuur meer met", "proSettings": "Pro instellingen", "proStats": "Je Pro statistieken", "proStatsTooltip": "Pro statistieken weerspiegelen het gebruik op dit apparaat en kunnen anders weergegeven worden op gekoppelde apparaten", - "proSupportDescription": "Hulp nodig met je Pro abonnement? Dien een verzoek in bij het ondersteuningsteam.", "proTosPrivacy": "Door bij te werken ga je akkoord met de Session Pro Gebruiksvoorwaarden {icon} en het Privacybeleid {icon}", "proUnlimitedPins": "Onbeperkte Pins", "proUnlimitedPinsDescription": "Organiseer al je chats met onbeperkt vastgezette gesprekken.", - "proUpdatePlanDescription": "Je zit momenteel op het {current_plan} abonnement. Weet je zeker dat je wilt overschakelen naar het {selected_plan} abonnement?

Als je dit bijwerkt, wordt je abonnement op {date} automatisch verlengd met {selected_plan} Pro toegang.", - "proUpdatePlanExpireDescription": "Je abonnement verloopt op {date}.

Door bij te werken wordt je abonnement automatisch verlengd op {date} voor een extra {selected_plan} aan Pro-toegang.", "proUserProfileModalCallToAction": "Wil je meer uit Session halen? Upgrade naar Session Pro voor een krachtigere berichtbeleving.", - "processingRefundRequest": "{platform_account} verwerkt je restitutieverzoek", "profile": "Profiel", "profileDisplayPicture": "Toon afbeelding", "profileDisplayPictureRemoveError": "Verwijderen profielfoto mislukt.", @@ -958,7 +924,6 @@ "recoveryPasswordWarningSendDescription": "Dit is uw herstelwachtwoord. Als u het naar iemand stuurt hebben ze volledige toegang tot uw account.", "recreateGroup": "Groep opnieuw aanmaken", "redo": "Opnieuw doen", - "refundPlanNonOriginatorApple": "Omdat je je oorspronkelijk hebt aangemeld voor Session Pro via een ander {platform_account}, moet je datzelfde {platform_account} gebruiken om je abonnement bij te werken.", "remainingCharactersOverTooltip": "Verkort berichtlengte met {count}", "remainingCharactersTooltip": "{count, plural, one [{count} teken resterend] other [{count} tekens resterend]}", "remove": "Verwijderen", @@ -1080,8 +1045,6 @@ "updateGroupInformationEnterShorterDescription": "Vul alstublieft een kortere groepsnaam in", "updateNewVersion": "Er is een nieuwe versie van Session beschikbaar, tik om bij te werken", "updateNewVersionDescription": "Versie ({version}) van Session is beschikbaar.", - "updatePlan": "Abonnement bijwerken", - "updatePlanTwo": "Twee manieren om je abonnement bij te werken:", "updateProfileInformation": "Profielinformatie bijwerken", "updateProfileInformationDescription": "Je weergavenaam en profielfoto zijn zichtbaar in alle gesprekken.", "updateReleaseNotes": "Ga naar Release Opmerkingen", @@ -1099,8 +1062,6 @@ "urlOpenDescriptionAlternative": "Links worden in uw browser geopend.", "usdNameShort": "USD", "useFastMode": "Gebruik Snelle Modus", - "viaStoreWebsite": "Via de {platform_store} website", - "viaStoreWebsiteDescription": "Wijzig je abonnement met het {platform_account} waarmee je je hebt aangemeld, via de {platform_store} website.", "video": "Video", "videoErrorPlay": "Kan video niet afspelen.", "view": "Bekijken", diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json index e0d3ea18b..3deb4e175 100644 --- a/_locales/pl/messages.json +++ b/_locales/pl/messages.json @@ -64,6 +64,8 @@ "appNameStocks": "Akcje", "appNameWeather": "Pogoda", "appPro": "Session Pro", + "appProBadge": "Odznaka Session Pro", + "appearanceAutoDarkMode": "Automatyczny tryb ciemny", "appearanceHideMenuBar": "Ukryj pasek menu", "appearanceLanguage": "Język", "appearanceLanguageDescription": "Wybierz ustawienie języka dla aplikacji Session. Po zmianie ustawienia języka aplikacja Session uruchomi się ponownie.", @@ -158,6 +160,7 @@ "blockUnblockNameMultiple": "Czy na pewno chcesz odblokować {name} i {count} innych użytkowników?", "blockUnblockNameTwo": "Czy na pewno chcesz odblokować użytkownika {name} i jedną inną osobę?", "blockUnblockedUser": "Odblokowano {name}", + "blockedContactsManageDescription": "Przeglądaj i zarządzaj zablokowanymi kontaktami.", "call": "Zadzwoń", "callsCalledYou": "{name} dzwonił(a) do Ciebie", "callsCannotStart": "Nie można rozpocząć nowego połączenia. Najpierw należy zakończyć bieżące połączenie.", @@ -182,6 +185,7 @@ "callsSettings": "Połączenia (Beta)", "callsVoiceAndVideo": "Połączenia głosowe i wideo", "callsVoiceAndVideoBeta": "Połączenia głosowe i wideo (beta)", + "callsVoiceAndVideoModalDescription": "Twój adres IP jest widoczny dla twojego rozmówcy i serwera Session Foundation, kiedy używasz rozmów beta.", "callsVoiceAndVideoToggleDescription": "Umożliwia wykonywanie połączeń głosowych i wideo do i od innych użytkowników.", "callsYouCalled": "Zadzwoniono do {name}", "callsYouMissedCallPermissions": "Nie odebrano połączenia od użytkownika {name}, ponieważ nie włączono funkcji „Połączenia głosowe i wideo” w ustawieniach prywatności.", @@ -192,7 +196,9 @@ "cameraGrantAccessDescription": "Aby robić zdjęcia, nagrywać filmy i skanować kody QR, aplikacja Session potrzebuje dostępu do aparatu", "cameraGrantAccessQr": "Aby skanować kody QR, aplikacja Session wymaga dostępu do aparatu", "cancel": "Anulowanie", + "change": "Zmień", "changePasswordFail": "Nie udało się zmienić hasła", + "changePasswordModalDescription": "Zmień hasło dla Session. Dane przechowywane lokalnie zostaną ponownie zaszyfrowane nowym hasłem.", "clear": "Wyczyść", "clearAll": "Wyczyść wszystko", "clearDataAll": "Wyczyść wszystkie dane", @@ -260,6 +266,7 @@ "contentDescriptionMessageComposition": "Treść wiadomości", "contentDescriptionQuoteThumbnail": "Miniatura obrazu z cytowanej wiadomości", "contentDescriptionStartConversation": "Utwórz rozmowę z nowym kontaktem", + "contentNotificationDescription": "Wybierz treść wyświetlaną w lokalnych powiadomieniach, kiedy pojawia się nowa wiadomość.", "conversationsAddToHome": "Dodaj do ekranu głównego", "conversationsAddedToHome": "Dodano do ekranu głównego", "conversationsAudioMessages": "Wiadomości audio", @@ -272,12 +279,18 @@ "conversationsDeleted": "Rozmowa usunięta", "conversationsEmpty": "W {conversation_name} nie ma żadnych wiadomości.", "conversationsEnter": "Klawisz Enter", + "conversationsEnterDescription": "Zdefiniuj jak klawisz Enter i kombinacja Shift+Enter działają w konwersacjach.", + "conversationsEnterNewLine": "SHIFT + ENTER wysyła wiadomość, ENTER zaczyna nowy wiersz.", + "conversationsEnterSends": "ENTER wysyła wiadomość, SHIFT + ENTER zaczyna nowy wiersz.", "conversationsGroups": "Grupy", "conversationsMessageTrimming": "Przycinanie wiadomości", "conversationsMessageTrimmingTrimCommunities": "Przytnij społeczności", + "conversationsMessageTrimmingTrimCommunitiesDescription": "Automatycznie usuwaj wiadomości starsze niż 6 miesięcy w społecznościach powyżej 2000 wiadomości.", "conversationsNew": "Nowa rozmowa", "conversationsNone": "Nie masz jeszcze żadnych konwersacji", + "conversationsSendWithEnterKey": "Wysyłaj przyciskiem Enter", "conversationsSendWithEnterKeyDescription": "Naciśnięcie klawisza Enter spowoduje wysłanie wiadomości zamiast rozpoczęcia nowej linijki.", + "conversationsSendWithShiftEnter": "Wysyłaj za pomocą Shift+Enter", "conversationsSettingsAllMedia": "Wszystkie media", "conversationsSpellCheck": "Sprawdzanie pisowni", "conversationsSpellCheckDescription": "Włącz sprawdzanie pisowni podczas pisania wiadomości.", @@ -286,7 +299,9 @@ "copy": "Kopiuj", "create": "Utwórz", "creatingCall": "Tworzenie połączenia", + "currentPassword": "Obecne hasło", "cut": "Wytnij", + "darkMode": "Tryb ciemny", "databaseErrorClearDataWarning": "Czy na pewno chcesz usunąć wszystkie wiadomości, załączniki i dane konta z tego urządzenia i utworzyć nowe konto?", "databaseErrorGeneric": "Wystąpił błąd bazy danych.

Wyeksportuj dzienniki aplikacji do udostępnienia w celu rozwiązania problemu. Jeśli to się nie powiedzie, zainstaluj ponownie Session i przywróć swoje konto.", "databaseErrorRestoreDataWarning": "Czy na pewno chcesz usunąć wszystkie wiadomości, załączniki i dane konta z tego urządzenia i przywrócić konto z sieci?", @@ -401,6 +416,7 @@ "emojiReactsHoverYouNameTwoDesktop": "Ty i użytkownik {name} zareagowaliście za pomocą: {emoji_name}", "emojiReactsNotification": "Zareagowano na Twoją wiadomość {emoji}", "enable": "Włącz", + "enableNotifications": "Wysyłaj powiadomienia o nowych wiadomościach.", "enjoyingSession": "Podoba Ci się Session?", "enjoyingSessionButtonNegative": "Wymaga poprawek {emoji}", "enjoyingSessionButtonPositive": "Jest świetnie {emoji}", @@ -412,8 +428,11 @@ "errorUnknown": "Wystąpił nieznany błąd.", "failedToDownload": "Nie udało się pobrać", "failures": "Awarie", + "feedback": "Feedback", + "feedbackDescription": "Podziel się wrażeniami o Session wypełniając krótką ankietę.", "file": "Plik", "files": "Pliki", + "followSystemSettings": "Dopasuj do ustawień systemu.", "forever": "Zawsze", "from": "Od:", "fullScreenToggle": "Pełny ekran", @@ -502,18 +521,24 @@ "groupUpdated": "Grupa zaktualizowana", "handlingConnectionCandidates": "Obsługa kandydatów do połączenia", "helpFAQ": "FAQ", + "helpFAQDescription": "Sprawdź FAQ Session by poznać odpowiedzi na często zadawane pytania.", "helpHelpUsTranslateSession": "Pomóż nam przetłumaczyć aplikację Session", + "helpReportABug": "Zgłoś błąd", "helpReportABugDescription": "Udostępnij szczegóły, które pomogą nam rozwiązać problem. Wyeksportuj dzienniki, a następnie prześlij plik za pośrednictwem działu pomocy aplikacji Session.", "helpReportABugExportLogs": "Eksportuj dzienniki", "helpReportABugExportLogsDescription": "Wyeksportuj dzienniki, a następnie prześlij plik przez dział pomocy aplikacji Session.", "helpReportABugExportLogsSaveToDesktop": "Zapisz na pulpicie", + "helpReportABugExportLogsSaveToDesktopDescription": "Zapisz ten plik i wyślij go do deweloperów Session.", "helpSupport": "Wsparcie techniczne", + "helpTranslateSessionDescription": "Pomóż przetłumaczyć Session na ponad 80 języków!", "helpWedLoveYourFeedback": "Chcielibyśmy poznać Twoją opinię", "hide": "Ukryj", + "hideMenuBarDescription": "Pokaż/ukryj pasek menu systemowego.", "hideNoteToSelfDescription": "Czy na pewno chcesz ukryć Moje notatki na liście konwersacji?", "hideOthers": "Ukryj pozostałe", "image": "Zdjęcie", "images": "obrazy", + "important": "Ważne", "incognitoKeyboard": "Klawiatura w trybie prywatnym", "incognitoKeyboardDescription": "Zażądaj trybu prywatnego, jeśli jest dostępny. W zależności od używanej klawiatury może ona zignorować to żądanie.", "info": "Informacje", @@ -546,6 +571,7 @@ "linkPreviewsSendModalDescription": "Podczas wysyłania podglądu linków nie wystęuje pełna ochrona metadanych.", "linkPreviewsTurnedOff": "Podglądy linków wyłączone", "linkPreviewsTurnedOffDescription": "Aby generować podgląd wysłanych i odbieranych linków, aplikacja Session musi połączyć się z powiązanymi stronami.

Możesz włączyć tę funkcję w ustawieniach aplikacji Session.", + "links": "Linki", "loadAccount": "Wczytywanie konta", "loadAccountProgressMessage": "Wczytywanie konta", "loading": "Wczytywanie...", @@ -558,7 +584,9 @@ "lockAppStatus": "Status blokady", "lockAppUnlock": "Naciśnij, aby odblokować", "lockAppUnlocked": "Session jest odblokowany", + "logs": "Dzienniki", "manageMembers": "Zarządzaj członkami", + "managePro": "Zarządzaj Pro", "max": "Maks", "media": "Multimedia", "members": "{count, plural, one [{count} członek] few [{count} członków] many [{count} członków] other [{count} członków]}", @@ -572,8 +600,10 @@ "membersInviteShareMessageHistory": "Udostępnij historię wiadomości", "membersInviteShareNewMessagesOnly": "Udostępniaj tylko nowe wiadomości", "membersInviteTitle": "Zaproś", + "menuBar": "Pasek Menu", "message": "Wiadomość", "messageBubbleReadMore": "Przeczytaj więcej", + "messageCopy": "Kopiuj wiadomość", "messageEmpty": "Wiadomość jest pusta.", "messageErrorDelivery": "Nie udało się dostarczyć wiadomości", "messageErrorLimit": "Osiągnięto limit wiadomości", @@ -625,7 +655,9 @@ "modalMessageTooLongDescription": "Skróć wiadomość do {limit} znaków lub mniej.", "modalMessageTooLongTitle": "Wiadomość jest za długa", "networkName": "Session Network", + "newPassword": "Nowe hasło", "next": "Następne", + "nextSteps": "Następne kroki", "nicknameDescription": "Wybierz pseudonim dla użytkownika {name}. Będzie on widoczny dla Ciebie w rozmowach prywatnych i grupowych.", "nicknameEnter": "Wprowadź pseudonim", "nicknameErrorShorter": "Wprowadź krótszy pseudonim", @@ -639,6 +671,9 @@ "noteToSelfEmpty": "Nie masz żadnych wiadomości w swoich notatkach.", "noteToSelfHide": "Ukryj swoje notatki", "noteToSelfHideDescription": "Czy na pewno chcesz ukryć swoją notatkę?", + "notificationDisplay": "Wyświetlanie powiadomień", + "notificationSenderNameAndPreview": "Wyświetlaj nazwę nadawcy i podgląd wiadomości.", + "notificationSenderNameOnly": "Wyświetlaj tylko nazwę nadawcy, bez podglądu wiadomości.", "notificationsAllMessages": "Wszystkie wiadomości", "notificationsContent": "Treść powiadomień", "notificationsContentDescription": "Informacje wyświetlane w powiadomieniach.", @@ -649,6 +684,7 @@ "notificationsFastModeDescription": "Dzięki serwerom powiadomień Google będziesz zawsze i natychmiast otrzymywać powiadomienia o nowych wiadomościach.", "notificationsFastModeDescriptionHuawei": "Będziesz otrzymywać powiadomienia o nowych wiadomościach niezawodnie i natychmiastowo, korzystając z serwerów powiadomień Huawei.", "notificationsFastModeDescriptionIos": "Se te notificará de nuevos mensajes de forma fiable e inmediata usando los servidores de notificaciones de Apple.", + "notificationsGenericOnly": "Wyświetlaj tylko powiadomienie Session, bez podglądu wiadomości ani nazwy nadawcy.", "notificationsGoToDevice": "Przejdź do ustawień powiadomień urządzenia", "notificationsHeaderAllMessages": "Powiadomienia – wszystkie", "notificationsHeaderMentionsOnly": "Powiadomienia – tylko wzmianki", @@ -656,6 +692,7 @@ "notificationsIosGroup": "{name} w konwersacji {conversation_name}", "notificationsIosRestart": "Możliwe, że podczas ponownego uruchamiania urządzenia {device} otrzymano wiadomości.", "notificationsLedColor": "Kolor LED", + "notificationsMakeSound": "Odtwórz dźwięk, kiedy otrzymasz nową wiadomość.", "notificationsMentionsOnly": "Tylko wzmianki", "notificationsMessage": "Powiadomienia o wiadomościach", "notificationsMostRecent": "Najnowsze od {name}", @@ -704,8 +741,12 @@ "openSurvey": "Otwórz ankietę", "other": "Inne", "oxenFoundation": "Oxen Foundation", + "password": "Hasło", "passwordChange": "Zmień hasło", + "passwordChangeShortDescription": "Zmień hasło wymagane do odblokowania Session.", + "passwordChangedDescriptionToast": "Twoje hasło zostało zmienione. Zapisz je w bezpiecznym miejscu.", "passwordConfirm": "Potwierdź hasło", + "passwordCreate": "Utwórz hasło", "passwordCurrentIncorrect": "Twoje obecne hasło jest nieprawidłowe.", "passwordEnter": "Wprowadź hasło", "passwordEnterCurrent": "Wprowadź aktualne hasło", @@ -715,8 +756,21 @@ "passwordErrorMatch": "Podane hasła różnią się", "passwordFailed": "Nie udało się ustawić hasła", "passwordIncorrect": "Nieprawidłowe hasło", + "passwordNewConfirm": "Potwierdź nowe hasło", "passwordRemove": "Usuń hasło", + "passwordRemoveShortDescription": "Usuń hasło wymagane do odblokowania Session", + "passwordRemovedDescriptionToast": "Twoje hasło zostało usunięte.", "passwordSet": "Ustaw hasło", + "passwordSetDescriptionToast": "Twoje hasło zostało utworzone. Zapisz je w bezpiecznym miejscu.", + "passwordSetShortDescription": "Wymagaj hasła do odblokowania Session przy uruchomieniu.", + "passwordStrengthCharLength": "Dłuższe niż 12 znaków", + "passwordStrengthIncludeNumber": "Zawiera cyfrę", + "passwordStrengthIncludesLowercase": "Zawiera małą literę", + "passwordStrengthIncludesSymbol": "Zawiera symbol", + "passwordStrengthIncludesUppercase": "Zawiera wielką literę", + "passwordStrengthIndicator": "Wskaźnik siły hasła", + "passwordStrengthIndicatorDescription": "Ustawienie silnego hasła pomaga chronić Twoje wiadomości i załączniki w przypadku utraty lub kradzieży urządzenia.", + "passwords": "Hasła", "paste": "Wklej", "permissionChange": "Zmiana uprawnień", "permissionMusicAudioDenied": "Aby wysyłać pliki, muzykę i dźwięk, aplikacja Session potrzebuje dostępu do muzyki i dźwięku, jednak na stałe go odmówiono. Naciśnij „Ustawienia” → „Uprawnienia” i włącz „Muzyka i dźwięk”.", @@ -729,6 +783,7 @@ "permissionsCameraDescriptionIos": "Zezwól na dostęp do kamery na potrzeby rozmów wideo.", "permissionsFaceId": "Funkcja blokady ekranu w aplikacji Session używa Face ID.", "permissionsKeepInSystemTray": "Trzymaj w zasobniku systemowym", + "permissionsKeepInSystemTrayDescription": "Session nadal działa w tle po zamknięciu okna.", "permissionsLibrary": "Aby kontynuować, aplikacja Session potrzebuje dostępu do biblioteki zdjęć. Dostęp można włączyć w ustawieniach systemu iOS.", "permissionsLocalNetworkAccessRequiredCallsIos": "Aby móc wykonywać połączenia, wymagany jest dostęp do sieci lokalnej. Aby kontynuować, przełącz uprawnienia „Sieć lokalna” w Ustawieniach.", "permissionsLocalNetworkAccessRequiredIos": "Session potrzebuje dostępu do sieci lokalnej, aby wykonywać połączenia głosowe i wideo.", @@ -755,18 +810,37 @@ "pinConversation": "Przypnij konwersację", "pinUnpin": "Odepnij", "pinUnpinConversation": "Odepnij konwersację", + "plusLoadsMoreDescription": "Nowe funkcje niedługo pojawią się w Pro. Odkryj, co nowego na Pro Roadmap {icon}", + "preferences": "Preferencje", "preview": "Podgląd", + "previewNotification": "Podgląd powiadomień", "pro": "Pro", "proActivated": "Aktywowano", + "proAllSet": "Wszystko gotowe!", "proAlreadyPurchased": "Masz już", "proAnimatedDisplayPicture": "Możesz przesyłać GIF-y i animowane obrazy WebP jako swoje zdjęcie profilowe!", "proAnimatedDisplayPictureCallToActionDescription": "Zyskaj animowane zdjęcia profilowe i odblokuj funkcje premium dzięki Session Pro", "proAnimatedDisplayPictureFeature": "Animowany obraz profilowy", "proAnimatedDisplayPictureModalDescription": "użytkownicy mogą przesyłać GIF-y", + "proAnimatedDisplayPictures": "Animowane obrazy profilu", + "proAnimatedDisplayPicturesDescription": "Ustawiaj animowane obrazy GIF i WebP jako swój obraz profilu.", "proAnimatedDisplayPicturesNonProModalDescription": "Przesyłaj GIF-y z", + "proAutoRenewTime": "Automatyczne odnowienie subskrypcji Pro: {time}", + "proBadge": "Odznaka Pro", + "proBadgeVisible": "Pokaż odznakę Session Pro innym użytkownikom", + "proBadges": "Odznaki", + "proBadgesDescription": "Pokaż swoje wsparcie dla Session ekskluzywną odznaką obok swojej nazwy wyświetlanej.", + "proBadgesSent": "{count, plural, one [Wysłano {total} odznakę Pro] few [Wysłano {total} odznaki Pro] many [Wysłano {total} odznak Pro] other [Wysłano {total} odznak Pro]}", + "proBetaFeatures": "Funkcje Pro", + "proBilledAnnually": "Opłata roczna: {price}", + "proBilledMonthly": "Opłata miesięczna: {price}", + "proBilledQuarterly": "Opłata kwartalna: {price}", "proCallToActionLongerMessages": "Chcesz wysyłać dłuższe wiadomości? Wyślij więcej tekstu i odblokuj funkcje premium dzięki Session Pro", "proCallToActionPinnedConversations": "Chcesz przypinać więcej czatów? Zorganizuj konwersacje i odblokuj funkcje premium dzięki Session Pro", "proCallToActionPinnedConversationsMoreThan": "Chcesz przypiąć więcej niż 5 czatów? Zorganizuj konwersacje i odblokuj funkcje premium dzięki Session Pro", + "proExpiringSoon": "Niedługo wygaśnie", + "proFaq": "FAQ Pro", + "proFaqDescription": "Znajdź odpowiedzi na często zadawane pytania w sekcji FAQ Session Pro.", "proFeatureListAnimatedDisplayPicture": "Prześlij obrazy profilowe w formacie GIF i WebP", "proFeatureListLargerGroups": "Większe czaty grupowe do 300 członków", "proFeatureListLoadsMore": "I wiele więcej ekskluzywnych funkcji", @@ -774,10 +848,28 @@ "proFeatureListPinnedConversations": "Przypinaj nieograniczoną liczbę konwersacji", "proGroupActivated": "Grupa została aktywowana", "proGroupActivatedDescription": "Ta grupa ma zwiększoną pojemność! Może obsługiwać do 300 członków, ponieważ administrator grupy posiada", + "proGroupsUpgraded": "{count, plural, one [Ulepszono {total} grupę] few [Ulepszono {total} grupy] many [Ulepszono {total} grup] other [Ulepszono {total} grup]}", "proIncreasedAttachmentSizeFeature": "Zwiększony rozmiar załączników", "proIncreasedMessageLengthFeature": "Zwiększona długość wiadomości", + "proLargerGroups": "Większe grupy", + "proLongerMessages": "Dłuższe wiadomości", + "proLongerMessagesDescription": "Możesz wysyłać wiadomości aż do 10 000 znaków we wszystkich konwersacjach.", + "proLongerMessagesSent": "{count, plural, one [Wysłano {total} dłuższą wiadomość] few [Wysłano {total} dłuższe wiadomości] many [Wysłano {total} dłuższych wiadomości] other [Wysłano {total} dłuższych wiadomości]}", "proMessageInfoFeatures": "Ta wiadomość zawierała następujące funkcje Session Pro:", + "proPinnedConversations": "{count, plural, one [{total} przypięta konwersacja] few [{total} przypięte konwersacje] many [{total} przypiętych konwersacji] other [{total} przypiętych konwersacji]}", + "proPriceOneMonth": "1 miesiąc - {monthly_price} / miesiąc", + "proPriceThreeMonths": "3 miesiące - {monthly_price} / miesiąc", + "proPriceTwelveMonths": "12 miesięcy - {monthly_price} / miesiąc", + "proRefundDescription": "Przykro nam, że odchodzisz. Tutaj znajdziesz wszystko, co powinieneś wiedzieć przed złożeniem wniosku o zwrot.", + "proRefunding": "Zwrot Pro", + "proRequestedRefund": "Wniosek o zwrot wysłany", "proSendMore": "Wyślij więcej z", + "proSettings": "Ustawienia Pro", + "proStats": "Twoje Statystyki Pro", + "proStatsTooltip": "Statystyki Pro pokazują użycie na tym urządzeniu i mogą wyglądać różnie na połączonych urządzeniach", + "proTosPrivacy": "Dokonując zmian wyrażasz zgodę na Warunki Świadczenia Usług Session Pro {icon} oraz Politykę Prywatności {icon}", + "proUnlimitedPins": "Nielimitowane przypięcia", + "proUnlimitedPinsDescription": "Organizuj swoje czaty z nielimitowaną możliwością przypinania konwersacji.", "proUserProfileModalCallToAction": "Chcesz więcej z Session? Uaktualnij do Session Pro, aby uzyskać potężniejsze możliwości wiadomości.", "profile": "Profil", "profileDisplayPicture": "Zdjęcie profilowe", @@ -809,6 +901,7 @@ "recommended": "Zalecane", "recoveryPasswordBannerDescription": "Zapisz hasło odzyskiwania, aby mieć pewność, że nie utracisz dostępu do konta.", "recoveryPasswordBannerTitle": "Zapisz swoje hasło odzyskiwania", + "recoveryPasswordDescription": "Użyj swojego hasła odzyskiwania, by załadować swoje konto na nowych urządzeniach.

Twoje konto nie może być odzyskane bez tego hasła. Upewnij się, że przechowujesz je w bezpiecznym miejscu – i nie ujawniaj go nikomu.", "recoveryPasswordEnter": "Wprowadź hasło odzyskiwania", "recoveryPasswordErrorLoad": "Wystąpił błąd podczas próby wczytania hasła odzyskiwania.

Wyeksportuj swoje dzienniki, a następnie prześlij plik za pośrednictwem pomocy technicznej aplikacji Session, aby pomóc w rozwiązaniu tego problemu.", "recoveryPasswordErrorMessageGeneric": "Sprawdź hasło odzyskiwania i spróbuj ponownie.", @@ -818,9 +911,12 @@ "recoveryPasswordExplanation": "Aby wczytać konto, wprowadź hasło odzyskiwania.", "recoveryPasswordHidePermanently": "Ukryj hasło odzyskiwania na stałe", "recoveryPasswordHidePermanentlyDescription1": "Bez hasła odzyskiwania nie można wczytać konta na nowych urządzeniach.

Zanim przejdziesz dalej, zdecydowanie zalecamy zapisanie hasła odzyskiwania w bezpiecznym miejscu.", + "recoveryPasswordHidePermanentlyDescription2": "Czy na pewno chcesz na stałe ukryć swoje hasło odzyskiwania na tym urządzeniu?

Nie można tego cofnąć.", "recoveryPasswordHideRecoveryPassword": "Ukryj hasło odzyskiwania", "recoveryPasswordHideRecoveryPasswordDescription": "Na stałe ukryj na tym urządzeniu hasło odzyskiwania konta.", "recoveryPasswordRestoreDescription": "Aby wczytać konto, wprowadź hasło odzyskiwania. Jeśli nie zostało ono zapisane, można je znaleźć w ustawieniach aplikacji.", + "recoveryPasswordView": "Pokaż hasło odzyskiwania", + "recoveryPasswordVisibility": "Widoczność hasła odzyskiwania", "recoveryPasswordWarningSendDescription": "To jest Twoje hasło odzyskiwania. Jeśli je komuś wyślesz, osoba ta będzie miała pełny dostęp do Twojego konta.", "recreateGroup": "Odtwórz Grupę", "redo": "Ponów", @@ -828,7 +924,9 @@ "remainingCharactersTooltip": "{count, plural, one [Pozostał {count} znak] few [Pozostały {count} znaki] many [Pozostało {count} znaków] other [Pozostało {count} znaków]}", "remove": "Usuń", "removePasswordFail": "Nie udało się usunąć hasła", + "removePasswordModalDescription": "Usuń swoje obecne hasło dla Session. Dane przechowywane lokalnie zostaną ponownie zaszyfrowane losowo wygenerowanym kluczem, przechowywanym na Twoim urządzeniu.", "reply": "Odpowiedz", + "requestRefund": "Zawnioskuj o zwrot", "resend": "Wyślij ponownie", "resolving": "Wczytywanie informacji o kraju...", "restart": "Uruchom ponownie", @@ -884,10 +982,12 @@ "sessionNotifications": "Powiadomienia", "sessionPermissions": "Uprawnienia", "sessionPrivacy": "Prywatność", + "sessionProBeta": "Beta Session Pro", "sessionRecoveryPassword": "Hasło odzyskiwania", "sessionSettings": "Ustawienia", "set": "Ustawiono", "setCommunityDisplayPicture": "Ustaw zdjęcie profilowe grupy", + "setPasswordModalDescription": "Ustaw hasło dla Session. Dane przechowywane lokalnie będą zaszyfrowane tym hasłem. Będziesz musiał je podać za każdym razem, kiedy uruchamiasz Session.", "settingsRestartDescription": "Aby zastosować nowe ustawienia, należy ponownie uruchomić aplikację Session.", "settingsScreenSecurityDesktop": "Ochrona ekranu", "share": "Udostępnij", @@ -901,18 +1001,24 @@ "showLess": "Pokaż mniej", "showNoteToSelf": "Pokaż moje notatki", "showNoteToSelfDescription": "Czy na pewno chcesz wyświetlać Moje notatki na liście konwersacji?", + "spellChecker": "Sprawdzanie pisowni", "stakingRewardPool": "Staking Reward Pool", "stickers": "Naklejki", + "strength": "Siła", + "supportDescription": "Masz problem? Przejrzyj artykuły pomocy lub utwórz zgłoszenie dla Supportu Session.", "supportGoTo": "Przejdź do strony wsparcia technicznego", "systemInformationDesktop": "Informacja systemowa: {information}", "tapToRetry": "Dotknij aby ponowić", "theContinue": "Kontynuuj", "theDefault": "Domyślne", "theError": "Błąd", + "theReturn": "Powrót", + "themePreview": "Podgląd motywu", "tokenNameLong": "Session Token", "tokenNameShort": "SESH", "tooltipAccountIdVisible": "Identyfikator konta {name} jest widoczny na podstawie wcześniejszych interakcji", "tooltipBlindedIdCommunities": "Zanonimizowane identyfikatory są używane w społecznościach w celu ograniczenia spamu i zwiększenia prywatności", + "translate": "Przetłumacz", "tryAgain": "Spróbuj ponownie", "typingIndicators": "Wskaźniki pisania", "typingIndicatorsDescription": "Wyświetlaj i udostępniaj wskaźniki pisania.", @@ -933,16 +1039,21 @@ "updateGroupInformationEnterShorterDescription": "Wprowadź krótszy opis grupy", "updateNewVersion": "Dostępna jest nowa wersja aplikacji Session. Stuknij, aby zaktualizować", "updateNewVersionDescription": "Dostępna jest nowa wersja ({version}) aplikacji Session.", + "updateProfileInformation": "Zaktualizuj informacje w profilu", + "updateProfileInformationDescription": "Twoja nazwa wyświetlana i obraz profilu są widoczne we wszystkich konwersacjach.", "updateReleaseNotes": "Przejdź do informacji o wersji", "updateSession": "Aktualizacja aplikacji Session", "updateVersion": "Wersja {version}", "updated": "Ostatnia aktualizacja {relative_time} temu", + "updates": "Aktualizacje", + "updating": "Aktualizowanie...", "upgradeTo": "Uaktualnij do", "uploading": "Przesyłanie", "urlCopy": "Skopiuj adres URL", "urlOpen": "Otwórz adres URL", "urlOpenBrowser": "Zostanie otwarte w przeglądarce.", "urlOpenDescription": "Czy na pewno chcesz otworzyć ten adres URL w przeglądarce?

{url}", + "urlOpenDescriptionAlternative": "Linki będą otwierane w Twojej przeglądarce.", "usdNameShort": "USD", "useFastMode": "Użyj trybu szybkiego", "video": "Wideo", @@ -955,6 +1066,9 @@ "warning": "Uwaga", "window": "Okno", "yes": "Tak", - "you": "Ty" + "you": "Ty", + "yourRecoveryPassword": "Twoje hasło odzyskiwania", + "zoomFactor": "Współczynnik powiększenia", + "zoomFactorDescription": "Dostosuj wielkość tekstu i elementów wizualnych." } diff --git a/_locales/uk/messages.json b/_locales/uk/messages.json index f2611b322..a8c0a6f33 100644 --- a/_locales/uk/messages.json +++ b/_locales/uk/messages.json @@ -196,7 +196,6 @@ "cameraGrantAccessDescription": "Session потребує доступ до камери, щоб фотографувати, знімати відео або сканувати QR-коди.", "cameraGrantAccessQr": "Session потрібен дозвіл до камери для сканування QR-кодів", "cancel": "Скасувати", - "cancelPlan": "Скасувати тарифний план", "change": "Змінити", "changePasswordFail": "Не вдалося змінити пароль", "changePasswordModalDescription": "Змінити ваш пароль для Session. Локально збережені дані будуть наново шифровані з застосуванням нового паролю.", @@ -301,7 +300,6 @@ "create": "Створити", "creatingCall": "Викликаємо", "currentPassword": "Поточний пароль", - "currentPlan": "Поточна передплата", "cut": "Вирізати", "darkMode": "Темний режим", "databaseErrorClearDataWarning": "Ви впевнені, що хочете видалити всі повідомлення, вкладення та дані облікового запису з цього пристрою та створити новий обліковий запис?", @@ -721,6 +719,7 @@ "off": "Вимкнено", "okay": "Добре", "on": "Увімк.", + "onDevice": "На вашому пристрої {device_type}", "onboardingAccountCreate": "Створити обліковий запис", "onboardingAccountCreated": "Обліковий запис створено", "onboardingAccountExists": "Я маю обліковий запис", @@ -773,6 +772,7 @@ "passwordStrengthCharLength": "Довший 12 символів", "passwordStrengthIncludeNumber": "Містить цифру", "passwordStrengthIncludesLowercase": "Містить літеру нижнього регістру", + "passwordStrengthIncludesSymbol": "Містить символ", "passwordStrengthIncludesUppercase": "Містить літеру верхнього регістру", "passwordStrengthIndicator": "Індикатор надійності паролю", "passwordStrengthIndicatorDescription": "Встановлення надійного пароля допомагає захистити ваші повідомлення та вкладення у разі втрати або крадіжки пристрою.", @@ -824,7 +824,6 @@ "pro": "Pro", "proActivated": "активовано", "proAllSet": "Готово!", - "proAllSetDescription": "Твою підписку Session Pro оновлено. {date} коли підписку Pro буде подовжено, тоді й стягнуть гроші.", "proAlreadyPurchased": "У вас вже є", "proAnimatedDisplayPicture": "Не зволікайте і завантажуйте GIF та анімовані WebP картинки для свого аватара!", "proAnimatedDisplayPictureCallToActionDescription": "Отримайте анімовані аватари та розблокуйте преміальні функції з Session Pro", @@ -838,29 +837,25 @@ "proBadgeVisible": "Показувати значок Session Pro іншим користувачам", "proBadges": "Позначки", "proBadgesDescription": "Продемонструйте свою підтримку Session з ексклюзивним значком поруч з власним іменем.", + "proBetaFeatures": "Можливості Pro", "proBilledAnnually": "{price} сплата щорічно", "proBilledMonthly": "{price} сплата щомісячно", "proBilledQuarterly": "{price} сплата щоквартально", "proCallToActionLongerMessages": "Хочете відправляти довші повідомлення? Надсилайте більше тексту та розблокуйте преміальні функції застосунку з Session Pro", "proCallToActionPinnedConversations": "Потрібно більше закріплених бесід? Впорядкуйте свої чати та розблокуйте преміальні функції з Session Pro", "proCallToActionPinnedConversationsMoreThan": "Потрібно понад 5 закріплених бесід? Впорядкуйте свої бесіди та розблокуйте преміальні функції з Session Pro", - "proDiscountTooltip": "На поточну підписку ти вже маєш знижку {percent}% від загальної ціни Session Pro.", "proExpired": "Підписка сплила", - "proExpiredDescription": "На жаль, підписка Pro сплила. Онови її задля збереження переваг і можливостей Session Pro.", "proExpiringSoon": "Невдовзі спливе підписка", - "proExpiringSoonDescription": "Підписка Pro спливе {time}. Онови підписку задля збереження переваг і можливостей Session Pro.", "proExpiringTime": "Pro спливає за {time}", "proFaq": "Pro ЧАП", - "proFaqDescription": "Відповіді на загальні запитання знайдеш у ЧаПи Session.", + "proFaqDescription": "Відповіді на загальні запитання знайдеш у ЧаПи Session Pro.", "proFeatureListAnimatedDisplayPicture": "Завантажуйте GIF та WebP аватари", "proFeatureListLargerGroups": "Більша кількість — до 300 учасників — групових чатів", "proFeatureListLoadsMore": "Та велика кількість ексклюзивних можливостей", "proFeatureListLongerMessages": "Повідомлення до 10 000 символів", "proFeatureListPinnedConversations": "Закріплюйте необмежену кількість бесід", - "proFeatures": "Можливості Pro", "proGroupActivated": "Групу активовано", "proGroupActivatedDescription": "У цієї групи розширено можливості! Тепер вона може вміщати до 300 учасників, тому що адміністратор групи має", - "proImportantDescription": "Вимагання повернення грошей закінчено. В разі схвалення твою підписку Pro негайно скасують і ти втратиш всі можливості Pro.", "proIncreasedAttachmentSizeFeature": "Збільшений розмір вкладення", "proIncreasedMessageLengthFeature": "Збільшена довжина повідомлень", "proLargerGroups": "Більші групи", @@ -868,14 +863,7 @@ "proLongerMessages": "Довші повідомлення", "proLongerMessagesDescription": "Ви можете надсилати повідомлення до 10 000 символів у всіх розмовах.", "proMessageInfoFeatures": "У цьому повідомленні наявні наступні функції Session Pro:", - "proPlanActivatedAuto": "Для тебе діє підписка Session Pro.

{date} твою підписку буде самодійно поновлено як {current_plan}. Оновлення підписки настане під час наступного оновлення Pro.", - "proPlanActivatedAutoShort": "Для тебе діє підписка Session Pro.

{date} твою підписку буде самодійно поновлено як {current_plan}.", - "proPlanActivatedNotAuto": "Твоя підписка Session Pro спливе {date}.

Для збереження особливих можливостей подовж свою підписку.", - "proPlanExpireDate": "Підписка Session Pro спливе {date}.", - "proPlanNotFound": "Передплата Pro не знайдена", - "proPlanRecover": "Відновити передплату Pro", - "proPlanRenew": "Оновити підписку Pro", - "proPlanRestored": "План Pro відновлено", + "proPercentOff": "{percent}% Знижки", "proPriceOneMonth": "1 місяць — {monthly_price} / місяць", "proPriceThreeMonths": "3 місяці — {monthly_price} / місяць", "proPriceTwelveMonths": "12 місяців – {monthly_price} / місяць", @@ -886,7 +874,6 @@ "proSettings": "Налаштування Pro", "proStats": "Ваша статистика Pro", "proStatsTooltip": "Звіти підписки Pro відображають використання лише цього пристрою, тож, мабуть, матимуть иншого вигляду на инших пристроях", - "proSupportDescription": "Якщо потребуєш допомоги щодо підписки Pro, надійшли звернення до відділу підтримки.", "proTosPrivacy": "Цією дією ти надаси згоду щодо дотримання Правил послуги Session Pro {icon} і Ставлення до особистих відомостей {icon}", "proUnlimitedPins": "Необмежена кількість закріплених бесід", "proUnlimitedPinsDescription": "Закріплення необмеженої кількості співрозмовників в головному переліку.", @@ -963,6 +950,8 @@ "screenSecurity": "Безпека перегляду", "screenshotNotifications": "Сповіщення про скриншот", "screenshotNotificationsDescription": "Отримувати сповіщення, коли контакт робить скриншот особистої бесіди.", + "screenshotProtectionDescriptionDesktop": "Приховувати вікно Session на знімках екрана, зроблених на цьому пристрої.", + "screenshotProtectionDesktop": "Захист від знімків екрана", "screenshotTaken": "{name} зробив скриншот.", "search": "Пошук", "searchContacts": "Пошук контактів", @@ -1063,8 +1052,6 @@ "updateGroupInformationEnterShorterDescription": "Будь ласка, введіть коротший опис групи", "updateNewVersion": "Нова версія Session доступна.", "updateNewVersionDescription": "Доступна нова версія ({version}) Session.", - "updatePlan": "Оновити тарифний план", - "updatePlanTwo": "Два шляхи поновлення твоєї підписки:", "updateProfileInformation": "Оновити інформацію облікового запису", "updateProfileInformationDescription": "Ваше відображуване ім’я та зображення профілю видимі у всіх розмовах.", "updateReleaseNotes": "Перейти в примітки до випуску", diff --git a/preload.js b/preload.js index 829de8dbb..556a68f6c 100644 --- a/preload.js +++ b/preload.js @@ -67,9 +67,20 @@ window.sessionFeatureFlags = { alwaysShowRemainingChars: false, showPopoverAnchors: false, proAvailable: !isEmpty(process.env.SESSION_PRO), + proGroupsAvailable: !isEmpty(process.env.SESSION_PRO_GROUPS), mockCurrentUserHasPro: !isEmpty(process.env.SESSION_USER_HAS_PRO), + mockCurrentUserHasProExpired: !isEmpty(process.env.SESSION_USER_HAS_PRO_EXPIRED), + mockCurrentUserHasProPlatformRefundExpired: !isEmpty( + process.env.SESSION_USER_HAS_PRO_PLATFORM_REFUND_EXPIRED + ), + mockCurrentUserHasProCancelled: !isEmpty(process.env.SESSION_USER_HAS_PRO_CANCELLED), + mockCurrentUserHasProInGracePeriod: !isEmpty(process.env.SESSION_USER_HAS_PRO_IN_GRACE), + mockProRecoverButtonAlwaysSucceed: !isEmpty(process.env.SESSION_PRO_RECOVER_ALWAYS_SUCCEED), + mockProRecoverButtonAlwaysFail: !isEmpty(process.env.SESSION_PRO_RECOVER_ALWAYS_FAIL), mockOthersHavePro: !isEmpty(process.env.SESSION_OTHERS_HAVE_PRO), mockMessageProFeatures: [], + mockProBackendLoading: !isEmpty(process.env.SESSION_PRO_BACKEND_LOADING), + mockProBackendError: !isEmpty(process.env.SESSION_PRO_BACKEND_ERROR), // Note: some stuff are init when the app starts, so fsTTL30s should only be set from the env itself (before app starts) fsTTL30s: !isEmpty(process.env.FILE_SERVER_TTL_30S), debugLogging: !isEmpty(process.env.SESSION_DEBUG), @@ -81,6 +92,12 @@ window.sessionFeatureFlags = { debugOnionRequests: false, }; +window.sessionFeatureFlagsWithData = { + mockProOriginatingPlatform: null, + mockProAccessVariant: null, + mockProAccessExpiry: null, +}; + window.versionInfo = { environment: window.getEnvironment(), version: window.getVersion(), diff --git a/stylesheets/_session_conversation.scss b/stylesheets/_session_conversation.scss index 312720e58..10582558f 100644 --- a/stylesheets/_session_conversation.scss +++ b/stylesheets/_session_conversation.scss @@ -64,21 +64,6 @@ .composition-container { border-top: 1px solid var(--border-color); z-index: 1; - - .session-icon-button { - display: flex; - justify-content: center; - align-items: center; - - margin-right: var(--margins-sm); - - .send { - padding: var(--margins-xs); - border-radius: 50%; - height: 30px; - width: 30px; - } - } } .session-recording { diff --git a/ts/components/SessionWrapperModal.tsx b/ts/components/SessionWrapperModal.tsx index 49d6cd99f..a0912d46c 100644 --- a/ts/components/SessionWrapperModal.tsx +++ b/ts/components/SessionWrapperModal.tsx @@ -34,7 +34,10 @@ type WithExtraRightButton = { }; type WithShowExitIcon = { showExitIcon?: boolean }; -const StyledModalHeader = styled(Flex)<{ bigHeader?: boolean; scrolled: boolean }>` +const StyledModalHeader = styled(Flex)<{ + bigHeader?: boolean; + scrolled: boolean; +}>` position: relative; font-family: var(--font-default); font-size: ${props => (props.bigHeader ? 'var(--font-size-h4)' : 'var(--font-size-xl)')}; diff --git a/ts/components/basic/SessionButton.tsx b/ts/components/basic/SessionButton.tsx index b0ce1396f..6100a1b54 100644 --- a/ts/components/basic/SessionButton.tsx +++ b/ts/components/basic/SessionButton.tsx @@ -21,6 +21,7 @@ export enum SessionButtonColor { Tertiary = 'background-tertiary', PrimaryDark = 'renderer-span-primary', // use primary in dark modes only since it has poor contrast in light mode Danger = 'danger', + Disabled = 'button-simple-disabled', None = 'transparent', } diff --git a/ts/components/buttons/ProButton.tsx b/ts/components/buttons/ProButton.tsx index cbd00825e..0bc0d55e4 100644 --- a/ts/components/buttons/ProButton.tsx +++ b/ts/components/buttons/ProButton.tsx @@ -30,14 +30,20 @@ export function ProIconButton({ onClick, dataTestId, style, + noColors, }: { iconSize: SessionIconSize; disabled?: boolean; onClick?: (() => void) | null; dataTestId: SessionDataTestId; style?: CSSProperties; + noColors?: boolean; }) { - const mergedStyle = { ...defaultStyle, ...style }; + const mergedStyle = { + ...defaultStyle, + ...(noColors ? { backgroundColor: 'var(--disabled-color)' } : {}), + ...style, + }; if (onClick) { return ( ` +export const StyledContent = styled.div<{ disabled?: boolean; rowReverse?: boolean }>` display: flex; + flex-direction: ${props => (props.rowReverse ? 'row-reverse' : 'row')}; justify-content: space-between; align-items: center; width: 100%; @@ -18,6 +19,8 @@ export const StyledContent = styled.div<{ disabled: boolean }>` `; const StyledPanelLabel = styled.p` + display: flex; + gap: var(--margins-xs); color: var(--text-secondary-color); margin: 0; align-self: flex-start; @@ -38,9 +41,11 @@ const StyledPanelLabelWithDescription = styled.div` export function PanelLabelWithDescription({ title, + extraInlineNode, description, }: { title: TrArgs; + extraInlineNode?: ReactNode; description?: TrArgs; }) { return ( @@ -48,6 +53,7 @@ export function PanelLabelWithDescription({ {/* less space between the label and the description */} + {extraInlineNode} {description ? ( @@ -58,12 +64,17 @@ export function PanelLabelWithDescription({ ); } -const StyledRoundedPanelButtonGroup = styled.div` +const StyledRoundedPanelButtonGroup = styled.div<{ isSidePanel?: boolean; isDarkTheme?: boolean }>` display: flex; flex-direction: column; justify-content: center; overflow: hidden; - background: var(--background-tertiary-color); + background: ${props => + props.isSidePanel + ? 'var(--background-tertiary-color)' + : props.isDarkTheme + ? 'var(--background-primary-color)' + : 'var(--background-secondary-color)'}; border-radius: 16px; // Note: we need no padding here so we can change the bg color on hover padding: 0; @@ -81,14 +92,20 @@ const PanelButtonContainer = styled.div` type PanelButtonGroupProps = { children: ReactNode; style?: CSSProperties; + isSidePanel?: boolean; }; export const PanelButtonGroup = ( props: PanelButtonGroupProps & { containerStyle?: CSSProperties } ) => { - const { children, style, containerStyle } = props; + const { children, style, containerStyle, isSidePanel } = props; + const isDarkTheme = useIsDarkTheme(); return ( - + {children} ); @@ -98,8 +115,10 @@ export const StyledPanelButton = styled.button<{ disabled: boolean; color?: string; isDarkTheme: boolean; + defaultCursorWhenDisabled?: boolean; }>` - cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')}; + cursor: ${props => + props.disabled ? (props.defaultCursorWhenDisabled ? 'default' : 'not-allowed') : 'pointer'}; display: flex; align-items: center; justify-content: space-between; @@ -181,6 +200,7 @@ type PanelButtonTextBaseProps = { export type PanelButtonSubtextProps = { subText: TrArgs; subTextDataTestId: SessionDataTestId; + subTextColorOverride?: string; }; const PanelButtonTextInternal = (props: PropsWithChildren) => { @@ -226,7 +246,10 @@ export const PanelButtonTextWithSubText = ( - + {props.extraSubTextNode} diff --git a/ts/components/buttons/panel/PanelChevronButton.tsx b/ts/components/buttons/panel/PanelChevronButton.tsx index 9ee869cc6..6b3f3ca54 100644 --- a/ts/components/buttons/panel/PanelChevronButton.tsx +++ b/ts/components/buttons/panel/PanelChevronButton.tsx @@ -5,15 +5,17 @@ import { GenericPanelButtonWithAction, type GenericPanelButtonProps, } from './GenericPanelButtonWithAction'; +import { SessionSpinner } from '../../loading'; type PanelChevronButtonProps = Pick & { onClick?: (...args: Array) => void; disabled?: boolean; baseDataTestId: SettingsChevron; + showAnimatedSpinnerIcon?: boolean; }; export const PanelChevronButton = (props: PanelChevronButtonProps) => { - const { onClick, disabled = false, baseDataTestId, textElement } = props; + const { onClick, disabled = false, baseDataTestId, textElement, showAnimatedSpinnerIcon } = props; return ( { rowDataTestId={`${baseDataTestId}-settings-row`} textElement={textElement} actionElement={ - + showAnimatedSpinnerIcon ? ( + + ) : ( + + ) } /> ); diff --git a/ts/components/buttons/panel/PanelIconButton.tsx b/ts/components/buttons/panel/PanelIconButton.tsx index e1be76afe..d87c9c664 100644 --- a/ts/components/buttons/panel/PanelIconButton.tsx +++ b/ts/components/buttons/panel/PanelIconButton.tsx @@ -20,22 +20,30 @@ type PanelIconButtonProps = Omit` flex-shrink: 0; - margin: 0 var(--margins-lg) 0 var(--margins-sm); + margin: ${props => + props.rowReverse + ? '0 var(--margins-sm) 0 var(--margins-lg)' + : '0 var(--margins-lg) 0 var(--margins-sm)'}; padding: 0; `; export const PanelIconButton = ( props: PanelIconButtonProps | (PanelIconButtonProps & PanelButtonSubtextProps) ) => { - const { text, color, disabled = false, onClick, dataTestId } = props; + const { text, color, disabled = false, onClick, rowReverse, dataTestId } = props; const subTextProps = 'subText' in props - ? { subText: props.subText, subTextDataTestId: props.subTextDataTestId } + ? { + subText: props.subText, + subTextDataTestId: props.subTextDataTestId, + subTextColorOverride: props.subTextColorOverride, + } : undefined; return ( @@ -47,15 +55,16 @@ export const PanelIconButton = ( color={color} style={{ minHeight: '55px' }} > - - {props.iconElement} - + + {props.iconElement} {subTextProps ? ( ) : ( diff --git a/ts/components/conversation/right-panel/overlay/RightPanelMedia.tsx b/ts/components/conversation/right-panel/overlay/RightPanelMedia.tsx index bf8eed661..2e214e8f6 100644 --- a/ts/components/conversation/right-panel/overlay/RightPanelMedia.tsx +++ b/ts/components/conversation/right-panel/overlay/RightPanelMedia.tsx @@ -162,7 +162,7 @@ export const RightPanelMedia = () => { > {displayName || PubKey.shorten(selectedConvoKey)} - + diff --git a/ts/components/conversation/right-panel/overlay/message-info/OverlayMessageInfo.tsx b/ts/components/conversation/right-panel/overlay/message-info/OverlayMessageInfo.tsx index 2f1dc6ff1..a80f56cbc 100644 --- a/ts/components/conversation/right-panel/overlay/message-info/OverlayMessageInfo.tsx +++ b/ts/components/conversation/right-panel/overlay/message-info/OverlayMessageInfo.tsx @@ -345,7 +345,7 @@ export const OverlayMessageInfo = () => { )} - + {/* CopyMessageBodyButton is always shown so the PanelButtonGroup always has at least one item */} {!isLegacyGroup && } {hasErrors && !isLegacyGroup && direction === 'outgoing' && ( diff --git a/ts/components/dialog/DeleteAccountModal.tsx b/ts/components/dialog/DeleteAccountModal.tsx index 872e104c4..118e40d9f 100644 --- a/ts/components/dialog/DeleteAccountModal.tsx +++ b/ts/components/dialog/DeleteAccountModal.tsx @@ -15,6 +15,7 @@ import { } from '../SessionWrapperModal'; import { ModalDescription } from './shared/ModalDescriptionContainer'; import { ModalFlexContainer } from './shared/ModalFlexContainer'; +import { useCurrentUserHasPro } from '../../hooks/useHasPro'; const DEVICE_ONLY = 'device_only' as const; const DEVICE_AND_NETWORK = 'device_and_network' as const; @@ -63,14 +64,19 @@ const DescriptionBeforeAskingConfirmation = (props: { }; const DescriptionWhenAskingConfirmation = (props: { deleteMode: DeleteModes }) => { + const hasPro = useCurrentUserHasPro(); return ( ); diff --git a/ts/components/dialog/LocalizedPopupDialog.tsx b/ts/components/dialog/LocalizedPopupDialog.tsx index 56074ce55..60855c016 100644 --- a/ts/components/dialog/LocalizedPopupDialog.tsx +++ b/ts/components/dialog/LocalizedPopupDialog.tsx @@ -6,13 +6,15 @@ import { type LocalizedPopupDialogState, updateLocalizedPopupDialog, } from '../../state/ducks/modalDialog'; -import { ModalBasicHeader, SessionWrapperModal } from '../SessionWrapperModal'; +import { + ModalActionsContainer, + ModalBasicHeader, + SessionWrapperModal, +} from '../SessionWrapperModal'; import { SessionButton, SessionButtonType } from '../basic/SessionButton'; -import { SpacerSM, SpacerXS } from '../basic/Text'; import { tr } from '../../localization/localeTools'; import { Localizer } from '../basic/Localizer'; import { ModalDescription } from './shared/ModalDescriptionContainer'; -import { ModalFlexContainer } from './shared/ModalFlexContainer'; const StyledScrollDescriptionContainer = styled.div` max-height: 150px; @@ -33,7 +35,8 @@ export function LocalizedPopupDialog(props: LocalizedPopupDialogState) { !props.title || !props.description || !props.title.token || - !props.description.token + !props.description.token || + (props.overrideButtons && !props.overrideButtons.length) ) { return null; } @@ -44,23 +47,42 @@ export function LocalizedPopupDialog(props: LocalizedPopupDialogState) { headerChildren={ } showExitIcon={true} /> } + buttonChildren={ + + {props.overrideButtons ? ( + props.overrideButtons.map( + ({ label, buttonType, onClick, closeAfterClick, dataTestId }) => ( + { + onClick?.(); + if (closeAfterClick) { + onClose(); + } + }} + dataTestId={dataTestId} + > + + + ) + ) + ) : ( + + {tr('okay')} + + )} + + } onClose={onClose} > - - - - - {tr('okay')} - - - ); } diff --git a/ts/components/dialog/SessionProInfoModal.tsx b/ts/components/dialog/SessionProInfoModal.tsx index 9656824a9..784dd4633 100644 --- a/ts/components/dialog/SessionProInfoModal.tsx +++ b/ts/components/dialog/SessionProInfoModal.tsx @@ -3,7 +3,11 @@ import { Dispatch, type ReactNode } from 'react'; import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import type { CSSProperties } from 'styled-components'; -import { type SessionProInfoState, updateSessionProInfoModal } from '../../state/ducks/modalDialog'; +import { + type SessionProInfoState, + updateSessionProInfoModal, + userSettingsModal, +} from '../../state/ducks/modalDialog'; import { SessionWrapperModal, WrapperModalWidth, @@ -243,7 +247,8 @@ function isProVisibleCTA(variant: SessionProInfoVariant): boolean { ].includes(variant); } -const buttonProps = { +// TODO: we might want to make this a specific button preset. As its used for all pro/sesh stuff +export const proButtonProps = { buttonShape: SessionButtonShape.Square, buttonType: SessionButtonType.Solid, fontWeight: 400, @@ -300,24 +305,36 @@ export function SessionProInfoModal(props: SessionProInfoState) { > {hasNoProAndNotGroupCta ? ( { + onClose(); + dispatch( + userSettingsModal({ + userSettingsPage: 'pro', + hideBackButton: true, + hideHelp: true, + centerAlign: true, + }) + ); + }} dataTestId="modal-session-pro-confirm-button" > {tr('theContinue')} ) : null} {tr(!hasNoProAndNotGroupCta ? 'close' : 'cancel')} diff --git a/ts/components/dialog/debug/DebugMenuModal.tsx b/ts/components/dialog/debug/DebugMenuModal.tsx index c28f5ac04..ad3d9acb1 100644 --- a/ts/components/dialog/debug/DebugMenuModal.tsx +++ b/ts/components/dialog/debug/DebugMenuModal.tsx @@ -18,7 +18,7 @@ import { SessionWrapperModal, WrapperModalWidth, } from '../../SessionWrapperModal'; -import { FeatureFlags } from './FeatureFlags'; +import { FeatureFlags, ProDebugSection } from './FeatureFlags'; import { ReleaseChannel } from './ReleaseChannel'; import { useHotkey } from '../../../hooks/useHotkey'; import { PopoverPlaygroundPage } from './playgrounds/PopoverPlaygroundPage'; @@ -64,6 +64,7 @@ function MainPage({ setPage }: DebugMenuPageProps) { return ( <> + diff --git a/ts/components/dialog/debug/FeatureFlags.tsx b/ts/components/dialog/debug/FeatureFlags.tsx index 2afc47169..d1a898ae7 100644 --- a/ts/components/dialog/debug/FeatureFlags.tsx +++ b/ts/components/dialog/debug/FeatureFlags.tsx @@ -1,5 +1,13 @@ import { isArray, isBoolean } from 'lodash'; -import type { SessionFlagsKeys } from '../../../state/ducks/types/releasedFeaturesReduxTypes'; +import { ReactNode, useEffect, useState } from 'react'; +import { + getFeatureFlag, + MockProAccessExpiryOptions, + SessionFeatureFlagKeys, + useDataFeatureFlag, + type SessionFeatureFlagWithDataKeys, + type SessionFlagsKeys, +} from '../../../state/ducks/types/releasedFeaturesReduxTypes'; import { Flex } from '../../basic/Flex'; import { SessionToggle } from '../../basic/SessionToggle'; import { HintText, SpacerSM, SpacerXS } from '../../basic/Text'; @@ -7,6 +15,8 @@ import { DEBUG_FEATURE_FLAGS } from './constants'; import { ConvoHub } from '../../../session/conversations'; import { isDebugMode } from '../../../shared/env_vars'; import { ProMessageFeature } from '../../../models/proMessageFeature'; +import { ProAccessVariant, ProOriginatingPlatform } from '../../../hooks/useHasPro'; +import { PanelButtonGroup } from '../../buttons'; type FeatureFlagToggleType = { forceUpdate: () => void; @@ -14,22 +24,28 @@ type FeatureFlagToggleType = { parentFlag?: SessionFlagsKeys; }; -const handleFeatureFlagToggle = ({ flag, parentFlag, forceUpdate }: FeatureFlagToggleType) => { - const currentValue = parentFlag - ? (window as any).sessionFeatureFlags[parentFlag][flag] - : (window as any).sessionFeatureFlags[flag]; - +const handleSetFeatureFlag = ({ + flag, + parentFlag, + forceUpdate, + value, +}: FeatureFlagToggleType & { value: boolean }) => { if (parentFlag) { - (window as any).sessionFeatureFlags[parentFlag][flag] = !currentValue; - window.log.debug(`[debugMenu] toggled ${parentFlag}.${flag} to ${!currentValue}`); + (window as any).sessionFeatureFlags[parentFlag][flag] = value; + window.log.debug(`[debugMenu] toggled ${parentFlag}.${flag} to ${value}`); } else { - (window as any).sessionFeatureFlags[flag] = !currentValue; - window.log.debug(`[debugMenu] toggled ${flag} to ${!currentValue}`); + (window as any).sessionFeatureFlags[flag] = value; + window.log.debug(`[debugMenu] toggled ${flag} to ${value}`); } forceUpdate(); - if (flag === 'proAvailable' || flag === 'mockCurrentUserHasPro' || flag === 'mockOthersHavePro') { + if ( + flag === 'proAvailable' || + flag === 'mockCurrentUserHasPro' || + flag === 'mockOthersHavePro' || + flag === 'mockCurrentUserHasProExpired' + ) { ConvoHub.use() .getConversations() .forEach(convo => { @@ -38,16 +54,44 @@ const handleFeatureFlagToggle = ({ flag, parentFlag, forceUpdate }: FeatureFlagT } }; +const handleFeatureFlagToggle = ({ flag, parentFlag, forceUpdate }: FeatureFlagToggleType) => { + const currentValue = parentFlag + ? (window as any).sessionFeatureFlags[parentFlag][flag] + : (window as any).sessionFeatureFlags[flag]; + handleSetFeatureFlag({ flag, parentFlag, forceUpdate, value: !currentValue }); +}; + export const FlagToggle = ({ flag, value, forceUpdate, parentFlag, + label, + visibleOnlyWithBooleanFlag, + hiddenAndDisabledWhenKeyEnabled, }: FeatureFlagToggleType & { value: any; + label?: string; + visibleOnlyWithBooleanFlag?: SessionFeatureFlagKeys; + hiddenAndDisabledWhenKeyEnabled?: SessionFeatureFlagKeys; }) => { const key = `feature-flag-toggle-${flag}`; - return ( + const visibleFlag = visibleOnlyWithBooleanFlag + ? getFeatureFlag(visibleOnlyWithBooleanFlag) + : true; + + const hideAndDisable = hiddenAndDisabledWhenKeyEnabled + ? getFeatureFlag(hiddenAndDisabledWhenKeyEnabled) + : false; + + useEffect(() => { + if (hideAndDisable) { + handleSetFeatureFlag({ flag, forceUpdate, value: false }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hideAndDisable]); + + return visibleFlag ? ( void handleFeatureFlagToggle({ flag, parentFlag, forceUpdate })} /> - {flag} + {label || flag} {DEBUG_FEATURE_FLAGS.DEV.includes(flag) ? Experimental : null} {DEBUG_FEATURE_FLAGS.UNTESTED.includes(flag) ? Untested : null} - ); + ) : null; +}; + +const handleFeatureFlagWithDataChange = ({ + flag, + value, + forceUpdate, +}: { + flag: SessionFeatureFlagWithDataKeys; + value: any; + forceUpdate: () => void; +}) => { + window.sessionFeatureFlagsWithData[flag] = value; + forceUpdate(); + + ConvoHub.use() + .getConversations() + .forEach(convo => { + convo.triggerUIRefresh(); + }); +}; + +type FlagDropdownInputProps = { + forceUpdate: () => void; + flag: SessionFeatureFlagWithDataKeys; + options: Array<{ label: string; value: number | null }>; + unsetOption: { label: string; value: number | null }; + visibleOnlyWithBooleanFlag?: SessionFeatureFlagKeys; + label: string; +}; + +export const FlagEnumDropdownInput = ({ + flag, + options, + forceUpdate, + unsetOption, + visibleOnlyWithBooleanFlag, + label, +}: FlagDropdownInputProps) => { + const key = `feature-flag-toggle-${flag}`; + const [selected, setSelected] = useState(() => { + const initValue = window.sessionFeatureFlagsWithData[flag]; + return Number.isFinite(initValue) ? initValue : unsetOption.value; + }); + const handleSelect = (newValue: number | null) => { + setSelected(newValue); + handleFeatureFlagWithDataChange({ + flag, + value: Number.isNaN(newValue) ? null : newValue, + forceUpdate, + }); + }; + + const visibleFlag = visibleOnlyWithBooleanFlag + ? getFeatureFlag(visibleOnlyWithBooleanFlag) + : true; + + return visibleFlag ? ( + + + + + ) : null; }; type FlagValues = boolean | object | string; @@ -96,13 +240,86 @@ function rotateMsgProFeat(currentValue: Array, forceUpdate: ( forceUpdate(); } +const proBooleanFlags: Array<{ + label: string; + key: SessionFeatureFlagKeys; + visibleWithParentKey?: SessionFeatureFlagKeys; + hiddenAndDisabledWhenKeyEnabled?: SessionFeatureFlagKeys; +}> = [ + { label: 'Pro Available', key: 'proAvailable' }, + { + label: 'Backend Loading', + key: 'mockProBackendLoading', + visibleWithParentKey: 'proAvailable', + hiddenAndDisabledWhenKeyEnabled: 'mockProBackendError', + }, + { + label: 'Backend Error', + key: 'mockProBackendError', + visibleWithParentKey: 'proAvailable', + hiddenAndDisabledWhenKeyEnabled: 'mockProBackendLoading', + }, + { + label: 'Recover always succeeds', + key: 'mockProRecoverButtonAlwaysSucceed', + visibleWithParentKey: 'proAvailable', + hiddenAndDisabledWhenKeyEnabled: 'mockProRecoverButtonAlwaysFail', + }, + { + label: 'Recover always fails', + key: 'mockProRecoverButtonAlwaysFail', + visibleWithParentKey: 'proAvailable', + hiddenAndDisabledWhenKeyEnabled: 'mockProRecoverButtonAlwaysSucceed', + }, + { + label: 'Pro Groups Available', + key: 'proGroupsAvailable', + visibleWithParentKey: 'proAvailable', + }, + { + label: 'Has Access', + key: 'mockCurrentUserHasPro', + visibleWithParentKey: 'proAvailable', + hiddenAndDisabledWhenKeyEnabled: 'mockCurrentUserHasProExpired', + }, + { + label: 'Access Expired', + key: 'mockCurrentUserHasProExpired', + visibleWithParentKey: 'proAvailable', + hiddenAndDisabledWhenKeyEnabled: 'mockCurrentUserHasPro', + }, + { + label: 'Platform Refund Expired', + key: 'mockCurrentUserHasProPlatformRefundExpired', + visibleWithParentKey: 'mockCurrentUserHasPro', + }, + { + label: 'Cancelled', + key: 'mockCurrentUserHasProCancelled', + visibleWithParentKey: 'mockCurrentUserHasPro', + }, + { + label: 'In Grace Period', + key: 'mockCurrentUserHasProInGracePeriod', + visibleWithParentKey: 'mockCurrentUserHasPro', + hiddenAndDisabledWhenKeyEnabled: 'mockCurrentUserHasProCancelled', + }, +]; + +const proBooleanFlagKeys = proBooleanFlags.map(({ key }) => key); + export const FeatureFlags = ({ - flags, + flags: _flags, forceUpdate, }: { flags: Record; forceUpdate: () => void; }) => { + const flags = Object.fromEntries( + Object.entries(_flags).filter( + ([key]) => !proBooleanFlagKeys.includes(key as SessionFeatureFlagKeys) + ) + ); return ( ); }; + +function DebugMenuSection({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} + +export const ProDebugSection = ({ forceUpdate }: { forceUpdate: () => void }) => { + const mockExpiry = useDataFeatureFlag('mockProAccessExpiry'); + return ( + + +

Session Pro

+
+ {proBooleanFlags.map( + ({ label, key, visibleWithParentKey, hiddenAndDisabledWhenKeyEnabled }) => ( + + ) + )} + + + + {mockExpiry ? ( + Mocked expiry time does not tick, it will keep being set to now + mock_expiry. + ) : null} + + +
+ ); +}; diff --git a/ts/components/dialog/debug/playgrounds/ProPlaygroundPage.tsx b/ts/components/dialog/debug/playgrounds/ProPlaygroundPage.tsx index f8eda262d..07919a494 100644 --- a/ts/components/dialog/debug/playgrounds/ProPlaygroundPage.tsx +++ b/ts/components/dialog/debug/playgrounds/ProPlaygroundPage.tsx @@ -29,6 +29,11 @@ export function ProPlaygroundPage() { flag="mockCurrentUserHasPro" value={useFeatureFlag('mockCurrentUserHasPro')} /> + { if (!modalState?.userSettingsPage) { @@ -42,6 +44,10 @@ export const UserSettingsDialog = (modalState: UserSettingsModalState) => { return ; case 'network': return ; + case 'pro': + return ; + case 'proNonOriginating': + return ; case 'message-requests': // the `message-request` is not a page of the user settings page, but a page in the left pane header currently. return null; diff --git a/ts/components/dialog/user-settings/components/SettingsChevronBasic.tsx b/ts/components/dialog/user-settings/components/SettingsChevronBasic.tsx index a31f8f619..fe21fb7bd 100644 --- a/ts/components/dialog/user-settings/components/SettingsChevronBasic.tsx +++ b/ts/components/dialog/user-settings/components/SettingsChevronBasic.tsx @@ -8,11 +8,15 @@ export function SettingsChevronBasic({ onClick, text, subText, + subTextColor, + loading, }: { text: TrArgs; subText: TrArgs; + subTextColor?: string; baseDataTestId: SettingsChevron; onClick: (() => Promise) | (() => void); + loading?: boolean; }) { return ( @@ -27,6 +32,7 @@ export function SettingsChevronBasic({ // eslint-disable-next-line @typescript-eslint/no-misused-promises onClick={onClick} baseDataTestId={baseDataTestId} + showAnimatedSpinnerIcon={loading} /> ); } diff --git a/ts/components/dialog/user-settings/components/UserSettingsModalContainer.tsx b/ts/components/dialog/user-settings/components/UserSettingsModalContainer.tsx index 70f1b82a4..c9ad01b89 100644 --- a/ts/components/dialog/user-settings/components/UserSettingsModalContainer.tsx +++ b/ts/components/dialog/user-settings/components/UserSettingsModalContainer.tsx @@ -5,11 +5,13 @@ export function UserSettingsModalContainer({ headerChildren, buttonChildren, onClose, + centerAlign, }: { children: React.ReactNode; headerChildren: React.ReactNode; buttonChildren?: React.ReactNode; onClose?: () => void; + centerAlign?: boolean; }) { return ( {children} diff --git a/ts/components/dialog/user-settings/pages/DefaultSettingsPage.tsx b/ts/components/dialog/user-settings/pages/DefaultSettingsPage.tsx index 3e2d8c99c..8e1b5452e 100644 --- a/ts/components/dialog/user-settings/pages/DefaultSettingsPage.tsx +++ b/ts/components/dialog/user-settings/pages/DefaultSettingsPage.tsx @@ -6,7 +6,6 @@ import { useOurConversationUsername, useOurAvatarPath } from '../../../../hooks/ import { UserUtils, ToastUtils } from '../../../../session/utils'; import { resetConversationExternal } from '../../../../state/ducks/conversations'; import { - updateSessionProInfoModal, onionPathModal, updateConversationDetailsModal, userSettingsModal, @@ -26,7 +25,6 @@ import { SessionIconButton, SessionLucideIconButton } from '../../../icon/Sessio import { QRView } from '../../../qrview/QrView'; import { ModalBasicHeader } from '../../../SessionWrapperModal'; import { showLinkVisitWarningDialog } from '../../OpenUrlModal'; -import { SessionProInfoVariant } from '../../SessionProInfoModal'; import { ModalPencilIcon } from '../../shared/ModalPencilButton'; import { ProfileHeader, ProfileName } from '../components'; import type { ProfileDialogModes } from '../ProfileDialogModes'; @@ -36,6 +34,7 @@ import { setDebugMode } from '../../../../state/ducks/debug'; import { useHideRecoveryPasswordEnabled } from '../../../../state/selectors/settings'; import { OnionStatusLight } from '../../OnionStatusPathDialog'; import { UserSettingsModalContainer } from '../components/UserSettingsModalContainer'; +import { useCurrentUserHasExpiredPro, useCurrentUserHasPro } from '../../../../hooks/useHasPro'; const handleKeyQRMode = (mode: ProfileDialogModes, setMode: (mode: ProfileDialogModes) => void) => { switch (mode) { @@ -92,10 +91,13 @@ function SessionProSection() { const dispatch = useDispatch(); const isProAvailable = useIsProAvailable(); + const userHasPro = useCurrentUserHasPro(); + const currentUserHasExpiredPro = useCurrentUserHasExpiredPro(); if (!isProAvailable) { return null; } + return ( } - text={{ token: 'appPro' }} + text={{ + token: userHasPro + ? 'sessionProBeta' + : currentUserHasExpiredPro + ? 'proRenewBeta' + : 'upgradeSession', + }} onClick={() => { - dispatch(updateSessionProInfoModal({ variant: SessionProInfoVariant.GENERIC })); + dispatch(userSettingsModal({ userSettingsPage: 'pro' })); }} dataTestId="session-pro-settings-menu-item" color="var(--renderer-span-primary-color)" diff --git a/ts/components/dialog/user-settings/pages/PrivacySettingsPage.tsx b/ts/components/dialog/user-settings/pages/PrivacySettingsPage.tsx index 890b01cb0..6119bb426 100644 --- a/ts/components/dialog/user-settings/pages/PrivacySettingsPage.tsx +++ b/ts/components/dialog/user-settings/pages/PrivacySettingsPage.tsx @@ -234,7 +234,7 @@ export function PrivacySettingsPage(modalState: UserSettingsModalState) { }} toggleDataTestId={'enable-typing-indicators-settings-toggle'} rowDataTestId={'enable-typing-indicators-settings-row'} - />{' '} + /> diff --git a/ts/components/dialog/user-settings/pages/network/sections/network/NetworkSection.tsx b/ts/components/dialog/user-settings/pages/network/sections/network/NetworkSection.tsx index b9daac88b..b4eaf6e9e 100644 --- a/ts/components/dialog/user-settings/pages/network/sections/network/NetworkSection.tsx +++ b/ts/components/dialog/user-settings/pages/network/sections/network/NetworkSection.tsx @@ -156,8 +156,10 @@ const CurrentPriceBlock = () => { $alignItems="flex-start" paddingInline={'12px 0'} paddingBlock={'var(--margins-md)'} - backgroundColor={isDarkTheme ? undefined : 'var(--background-secondary-color)'} - borderColor={isDarkTheme ? undefined : 'var(--transparent-color)'} + backgroundColor={ + isDarkTheme ? 'var(--background-primary-color)' : 'var(--background-secondary-color)' + } + borderColor={'var(--transparent-color)'} > @@ -227,8 +229,10 @@ const SecuredByBlock = () => { width={'100%'} paddingInline={'12px 0'} paddingBlock={'var(--margins-md)'} - backgroundColor={isDarkTheme ? undefined : 'var(--background-secondary-color)'} - borderColor={isDarkTheme ? undefined : 'var(--transparent-color)'} + backgroundColor={ + isDarkTheme ? 'var(--background-primary-color)' : 'var(--background-secondary-color)' + } + borderColor={'var(--transparent-color)'} > {tr('sessionNetworkSecuredBy')} diff --git a/ts/components/dialog/user-settings/pages/user-pro/ProNonOriginatingPage.tsx b/ts/components/dialog/user-settings/pages/user-pro/ProNonOriginatingPage.tsx new file mode 100644 index 000000000..9ab5918db --- /dev/null +++ b/ts/components/dialog/user-settings/pages/user-pro/ProNonOriginatingPage.tsx @@ -0,0 +1,596 @@ +import styled from 'styled-components'; +import { type ReactNode } from 'react'; +import { useDispatch } from 'react-redux'; +import { tr } from '../../../../../localization/localeTools'; +import { Localizer } from '../../../../basic/Localizer'; +import { ModalBasicHeader } from '../../../../SessionWrapperModal'; +import { ModalBackButton } from '../../../shared/ModalBackButton'; +import { ModalFlexContainer } from '../../../shared/ModalFlexContainer'; +import { UserSettingsModalContainer } from '../../components/UserSettingsModalContainer'; +import { useUserSettingsBackAction, useUserSettingsCloseAction } from '../userSettingsHooks'; +import { ProHeroImage } from './ProSettingsPage'; +import { assertUnreachable } from '../../../../../types/sqlSharedTypes'; +import { PanelButtonGroup } from '../../../../buttons'; +import { StyledContent } from '../../../../buttons/panel/PanelButton'; +import { LucideIcon } from '../../../../icon/LucideIcon'; +import { LUCIDE_ICONS_UNICODE, WithLucideUnicode } from '../../../../icon/lucide'; +import { SessionButton, SessionButtonColor } from '../../../../basic/SessionButton'; +import { showLinkVisitWarningDialog } from '../../../OpenUrlModal'; +import { proButtonProps } from '../../../SessionProInfoModal'; +import { Flex } from '../../../../basic/Flex'; +import type { ProNonOriginatingPageVariant } from '../../../../../types/ReduxTypes'; +import { ProOriginatingPlatform, useProAccessDetails } from '../../../../../hooks/useHasPro'; + +type VariantPageProps = { + variant: ProNonOriginatingPageVariant; +}; + +function ProStatusTextUpdate() { + const { data } = useProAccessDetails(); + return data.autoRenew ? ( + + ) : ( + + ); +} + +function ProPageHero({ variant }: VariantPageProps) { + switch (variant) { + case 'upgrade': + return ; + case 'update': + return } />; + case 'renew': + return ; + case 'cancel': + return ; + case 'refund': + return ; + default: + return assertUnreachable(variant, `Unknown pro non originating page variant: ${variant}`); + } +} + +const ProInfoBlockTitle = styled.div` + font-size: var(--font-size-xl); + line-height: var(--font-size-xl); + font-weight: 700; +`; + +const ProInfoBlockDescription = styled.div` + font-size: var(--font-size-md); + line-height: var(--font-size-md); +`; + +const ProInfoBlockSectionSubtitle = styled.div` + font-size: var(--font-size-md); + line-height: var(--font-size-md); + color: var(--text-secondary-color); +`; + +function ProInfoBlockItem({ + textElement, + iconElement, +}: { + iconElement: ReactNode; + textElement: ReactNode; +}) { + return ( + + + {iconElement} + {textElement} + + + ); +} + +const StyledBlockItemIcon = styled.div` + display: flex; + justify-content: center; + flex-shrink: 0; + width: 38px; + height: 38px; + padding: 0; + border-radius: var(--margins-xs); + color: var(--primary-color); +`; + +function ProInfoBlockIconElement({ unicode }: WithLucideUnicode) { + return ( + + + + + + ); +} + +const ProInfoBlockText = styled.div` + display: flex; + flex-direction: column; + align-items: start; + text-align: start; + gap: var(--margins-xs); +`; + +function ProInfoBlockDevice({ textElement }: { textElement: ReactNode }) { + const { data } = useProAccessDetails(); + return ( + } + textElement={ + + {tr('onDevice', { device_type: data.platformStrings.device_type })} + {textElement} + + } + /> + ); +} + +function ProInfoBlockDeviceLinked() { + const { data } = useProAccessDetails(); + return ( + } + textElement={ + + {tr('onLinkedDevice')} + + + } + /> + ); +} + +function ProInfoBlockWebsite({ textElement }: { textElement: ReactNode }) { + const { data } = useProAccessDetails(); + return ( + } + textElement={ + + {tr('viaStoreWebsite', { platform: data.platformStrings.platform })} + {textElement} + + } + /> + ); +} + +function ProInfoBlockLayout({ + titleElement, + descriptionElement, + descriptionOnClick, + subtitleElement, + blockItems, +}: { + titleElement: ReactNode; + descriptionElement: ReactNode; + descriptionOnClick?: () => void; + subtitleElement: ReactNode; + blockItems: ReactNode; +}) { + return ( + + {titleElement} + + {descriptionElement} + + {subtitleElement} + + {blockItems} + + + ); +} + +function ProInfoBlockUpgrade() { + const dispatch = useDispatch(); + const { data } = useProAccessDetails(); + return ( + + } + descriptionOnClick={() => + showLinkVisitWarningDialog('https://getsession.org/pro-roadmap', dispatch) + } + subtitleElement={ + {tr('proUpgradeOption')} + } + blockItems={} + /> + ); +} + +function ProInfoBlockUpdate() { + const { data } = useProAccessDetails(); + return ( + + } + subtitleElement={ + {tr('updateAccessTwo')} + } + blockItems={ + <> + + } + /> + + } + /> + + } + /> + ); +} + +function ProInfoBlockRenew() { + const { data } = useProAccessDetails(); + return ( + + } + subtitleElement={ + + {tr('proOptionsTwoRenewalSubtitle')} + + } + blockItems={ + <> + + + } + /> + + } + /> + ); +} + +function ProInfoBlockCancel() { + const { data } = useProAccessDetails(); + return ( + + } + subtitleElement={ + {tr('proCancellationOptions')} + } + blockItems={ + <> + + } + /> + + } + /> + + } + /> + ); +} + +const ProInfoBlockRefundTitle = styled.div` + font-size: var(--font-size-lg); + line-height: var(--font-size-lg); + font-weight: 700; + padding-top: var(--margins-xs); +`; + +function ProInfoBlockRefundSessionSupport() { + return ( + + + + + + + + + + + ); +} + +function ProInfoBlockRefundGooglePlay() { + const { data } = useProAccessDetails(); + return ( + + + + + + + + + + + ); +} + +function ProInfoBlockRefundIOS() { + const { data } = useProAccessDetails(); + return ( + + } + subtitleElement={ + {tr('refundRequestOptions')} + } + blockItems={ + <> + + } + /> + + } + /> + + } + /> + ); +} + +function ProInfoBlockRefund() { + const { data } = useProAccessDetails(); + + if (!data.isPlatformRefundAvailable) { + return ; + } + + switch (data.platform) { + case ProOriginatingPlatform.iOSAppStore: + return ; + case ProOriginatingPlatform.GooglePlayStore: + return ; + case ProOriginatingPlatform.Nil: + return ; + default: + return assertUnreachable(data.platform, `Unknown pro originating platform: ${data.platform}`); + } +} + +function ProInfoBlock({ variant }: VariantPageProps) { + switch (variant) { + case 'upgrade': + return ; + case 'update': + return ; + case 'cancel': + return ; + case 'refund': + return ; + case 'renew': + return ; + default: + return assertUnreachable(variant, `Unknown pro non originating page variant: ${variant}`); + } +} + +function ProPageButtonUpdate() { + const dispatch = useDispatch(); + const { data } = useProAccessDetails(); + return ( + { + showLinkVisitWarningDialog(data.platformStrings.platform_link_manage, dispatch); + }} + dataTestId="pro-open-platform-website-button" + > + + + ); +} + +function ProPageButtonCancel() { + const dispatch = useDispatch(); + const { data } = useProAccessDetails(); + return ( + { + showLinkVisitWarningDialog(data.platformStrings.platform_link_cancel, dispatch); + }} + dataTestId="pro-open-platform-website-button" + > + + + ); +} + +function ProPageButtonRefund() { + const dispatch = useDispatch(); + const { data } = useProAccessDetails(); + return ( + { + showLinkVisitWarningDialog( + data.isPlatformRefundAvailable + ? data.platformStrings.platform_link_refund + : data.platformStrings.session_support_link_refund, + dispatch + ); + }} + dataTestId="pro-open-platform-website-button" + > + {data.isPlatformRefundAvailable ? ( + + ) : ( + + )} + + ); +} + +function ProPageButton({ variant }: VariantPageProps) { + switch (variant) { + case 'upgrade': + return null; + case 'update': + case 'renew': + return ; + case 'cancel': + return ; + case 'refund': + return ; + default: + return assertUnreachable(variant, `Unknown pro non originating page variant: ${variant}`); + } +} + +export function ProNonOriginatingPage(modalState: { + userSettingsPage: 'proNonOriginating'; + nonOriginatingVariant: ProNonOriginatingPageVariant; + overrideBackAction?: () => void; + centerAlign?: boolean; +}) { + const backAction = useUserSettingsBackAction(modalState); + const closeAction = useUserSettingsCloseAction(modalState); + + const backOnClick = modalState.overrideBackAction || backAction; + + return ( + : undefined} + /> + } + onClose={closeAction || undefined} + centerAlign={modalState.centerAlign} + > + + + + + + + ); +} diff --git a/ts/components/dialog/user-settings/pages/user-pro/ProSettingsPage.tsx b/ts/components/dialog/user-settings/pages/user-pro/ProSettingsPage.tsx new file mode 100644 index 000000000..bf844151a --- /dev/null +++ b/ts/components/dialog/user-settings/pages/user-pro/ProSettingsPage.tsx @@ -0,0 +1,1007 @@ +import { isNumber } from 'lodash'; +import { MouseEventHandler, SessionDataTestId, useCallback, useMemo, type ReactNode } from 'react'; +import styled from 'styled-components'; +import { useDispatch } from 'react-redux'; +import { ModalBasicHeader } from '../../../../SessionWrapperModal'; +import { useUserSettingsBackAction, useUserSettingsCloseAction } from '../userSettingsHooks'; +import { + LocalizedPopupDialogButtonOptions, + updateLocalizedPopupDialog, + userSettingsModal, +} from '../../../../../state/ducks/modalDialog'; +import { ModalBackButton } from '../../../shared/ModalBackButton'; +import { UserSettingsModalContainer } from '../../components/UserSettingsModalContainer'; +import { ModalFlexContainer } from '../../../shared/ModalFlexContainer'; +import { + PanelButtonGroup, + PanelLabelWithDescription, + StyledContent, + StyledPanelButton, +} from '../../../../buttons/panel/PanelButton'; +import { SettingsExternalLinkBasic } from '../../components/SettingsExternalLinkBasic'; +import { showLinkVisitWarningDialog } from '../../../OpenUrlModal'; +import { PanelIconButton, PanelIconLucideIcon } from '../../../../buttons/panel/PanelIconButton'; +import { LUCIDE_ICONS_UNICODE, type WithLucideUnicode } from '../../../../icon/lucide'; +import { SettingsChevronBasic } from '../../components/SettingsChevronBasic'; +import { SettingsToggleBasic } from '../../components/SettingsToggleBasic'; +import { SessionTooltip } from '../../../../SessionTooltip'; +import { tr, type TrArgs } from '../../../../../localization/localeTools'; +import { LucideIcon } from '../../../../icon/LucideIcon'; +import { Storage } from '../../../../../util/storage'; +import { SettingsKey } from '../../../../../data/settings-key'; +import { getBrowserLocale } from '../../../../../util/i18n/shared'; +import { SessionIcon } from '../../../../icon'; +import { ProIconButton } from '../../../../buttons/ProButton'; +import { useIsDarkTheme } from '../../../../../state/theme/selectors/theme'; +import { Flex } from '../../../../basic/Flex'; +import { Localizer } from '../../../../basic/Localizer'; +import { + useCurrentUserHasPro, + useCurrentUserHasExpiredPro, + useCurrentNeverHadPro, + useProAccessDetails, +} from '../../../../../hooks/useHasPro'; +import { SessionButton, SessionButtonColor } from '../../../../basic/SessionButton'; +import { proButtonProps } from '../../../SessionProInfoModal'; +import { useIsProGroupsAvailable } from '../../../../../hooks/useIsProAvailable'; +import { SessionSpinner } from '../../../../loading'; +import { SpacerMD } from '../../../../basic/Text'; +import { sleepFor } from '../../../../../session/utils/Promise'; + +// TODO: There are only 2 props here and both are passed to the nonorigin modal dispatch, can probably be in their own object +type SectionProps = { + returnToThisModalAction: () => void; + centerAlign?: boolean; +}; + +const SectionFlexContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +`; + +const HeroImageBgContainer = styled.div` + height: 220px; +`; + +const HeroImageBg = styled.div<{ noColors?: boolean }>` + position: absolute; + left: 0; + right: 0; + top: 15%; + + justify-items: center; + + &::before { + content: ''; + position: absolute; + top: 20%; + left: -40%; + width: 180%; + height: 80%; + background: radial-gradient( + circle, + color-mix( + in srgb, + ${props => (props.noColors ? 'var(--disabled-color) 35%' : 'var(--primary-color) 25%')}, + transparent + ) + 0%, + transparent 70% + ); + filter: blur(35px); + + z-index: -1; /* behind the logo */ + } +`; + +const HeroImageLabelContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: var(--margins-sm); + padding: var(--margins-md); + + img:first-child { + transition: var(--duration-session-logo-text); + filter: var(--session-logo-text-current-filter); + -webkit-user-drag: none; + } +`; + +export const StyledProStatusText = styled.div<{ isError?: boolean }>` + text-align: center; + line-height: var(--font-size-sm); + font-size: var(--font-size-sm); + ${props => (props.isError ? 'color: var(--warning-color);' : '')} +`; + +export const StyledProHeroText = styled.div` + text-align: center; + line-height: var(--font-size-md); + font-size: var(--font-size-md); +`; + +type ProHeroImageProps = { + noColors?: boolean; + isError?: boolean; + heroStatusText?: ReactNode; + heroText?: ReactNode; + onClick?: MouseEventHandler; +}; + +export function ProHeroImage({ + noColors, + isError, + heroStatusText, + heroText, + onClick, +}: ProHeroImageProps) { + return ( + + + + + + + full-brand-text + + + + + + {heroStatusText ? ( + {heroStatusText} + ) : null} + {heroStatusText && heroText ? : null} + {heroText ? {heroText} : null} + + ); +} + +function useBackendErrorDialogButtons() { + const dispatch = useDispatch(); + const { refetch } = useProAccessDetails(); + + const buttons = useMemo(() => { + return [ + { + label: { token: 'retry' }, + dataTestId: 'pro-backend-error-retry-button', + onClick: refetch, + closeAfterClick: true, + }, + { + label: { token: 'helpSupport' }, + dataTestId: 'pro-backend-error-support-button', + onClick: () => { + showLinkVisitWarningDialog('https://getsession.org/pro-form', dispatch); + }, + closeAfterClick: true, + }, + ] satisfies Array; + }, [dispatch, refetch]); + + return buttons; +} + +function ProNonProContinueButton({ returnToThisModalAction, centerAlign }: SectionProps) { + const dispatch = useDispatch(); + const neverHadPro = useCurrentNeverHadPro(); + const { isLoading, isError } = useProAccessDetails(); + + const backendErrorButtons = useBackendErrorDialogButtons(); + + const handleClick = useCallback(() => { + dispatch( + isError + ? updateLocalizedPopupDialog({ + title: { token: 'proStatusError' }, + description: { token: 'proStatusNetworkErrorDescription' }, + overrideButtons: backendErrorButtons, + }) + : isLoading + ? updateLocalizedPopupDialog({ + title: { token: 'proStatusLoading' }, + description: { token: 'proStatusLoadingDescription' }, + }) + : userSettingsModal({ + userSettingsPage: 'proNonOriginating', + nonOriginatingVariant: 'upgrade', + overrideBackAction: returnToThisModalAction, + centerAlign, + }) + ); + }, [dispatch, isLoading, isError, backendErrorButtons, centerAlign, returnToThisModalAction]); + + if (!neverHadPro) { + return null; + } + + return ( + + + + ); +} + +const StatsContainer = styled.div``; + +const StatsRowContainer = styled.div` + display: flex; + gap: var(--margins-sm); + padding: var(--margins-md); +`; + +const StatsItemContainer = styled.div` + display: flex; + flex-direction: column; + gap: var(--margins-sm); + align-items: center; + flex-direction: row; + width: 50%; +`; + +const StatsLabel = styled.div<{ disabled?: boolean }>` + font-size: var(--font-size-md); + color: ${props => (props.disabled ? 'var(--disabled-color)' : 'var(--text-primary-color)')}; + font-weight: 700; + cursor: default; +`; + +const proBoxShadow = '0 4px 4px 0 rgba(0, 0, 0, 0.25)'; + +function ProStats() { + const proLongerMessagesSent = Storage.get(SettingsKey.proLongerMessagesSent) || 0; + const proPinnedConversations = Storage.get(SettingsKey.proPinnedConversations) || 0; + const proBadgesSent = Storage.get(SettingsKey.proBadgesSent) || 0; + const proGroupsUpgraded = Storage.get(SettingsKey.proGroupsUpgraded) || 0; + + const isDarkTheme = useIsDarkTheme(); + + const formatter = useMemo( + () => + new Intl.NumberFormat(getBrowserLocale(), { + notation: 'compact', + compactDisplay: 'short', // Uses 'K', 'M', 'B' etc. + }), + [] + ); + + const proGroupsAvailable = useIsProGroupsAvailable(); + + const userHasPro = useCurrentUserHasPro(); + if (!userHasPro) { + return null; + } + + if ( + !isNumber(proBadgesSent) || + !isNumber(proPinnedConversations) || + !isNumber(proLongerMessagesSent) || + !isNumber(proGroupsUpgraded) + ) { + return null; + } + + return ( + + + + + } + /> + + + + + + + {tr('proLongerMessagesSent', { + count: proLongerMessagesSent, + total: formatter.format(proLongerMessagesSent).toLocaleLowerCase(), + })} + + + + + + {tr('proPinnedConversations', { + count: proPinnedConversations, + total: formatter.format(proPinnedConversations).toLocaleLowerCase(), + })} + + + + + + + + {tr('proBadgesSent', { + count: proBadgesSent, + total: formatter.format(proBadgesSent).toLocaleLowerCase(), + })} + + + + + + {tr('proGroupsUpgraded', { + count: proGroupsUpgraded, + total: formatter.format(proGroupsUpgraded).toLocaleLowerCase(), + })} + + + + + + + + + + ); +} + +function ProSettings({ returnToThisModalAction, centerAlign }: SectionProps) { + const dispatch = useDispatch(); + const userHasPro = useCurrentUserHasPro(); + const { data, isLoading, isError } = useProAccessDetails(); + const backendErrorButtons = useBackendErrorDialogButtons(); + + const handleUpdateAccessClick = useCallback(() => { + dispatch( + isError + ? updateLocalizedPopupDialog({ + title: { token: 'proAccessError' }, + description: { token: 'proAccessNetworkLoadError' }, + overrideButtons: backendErrorButtons, + }) + : isLoading + ? updateLocalizedPopupDialog({ + title: { token: 'proAccessLoading' }, + description: { token: 'proAccessLoadingDescription' }, + }) + : userSettingsModal({ + userSettingsPage: 'proNonOriginating', + nonOriginatingVariant: 'update', + overrideBackAction: returnToThisModalAction, + centerAlign, + }) + ); + }, [dispatch, isLoading, isError, backendErrorButtons, centerAlign, returnToThisModalAction]); + + if (!userHasPro) { + return ( + + ); + } + + return ( + + + + + { + throw new Error('Not implemented, and state {false} too'); + }} + active={false} + /> + + + ); +} + +function ProFeatureItem({ + textElement, + iconElement, + dataTestId, + onClick, +}: { + iconElement: ReactNode; + textElement: ReactNode; + dataTestId: SessionDataTestId; + onClick?: () => Promise; +}) { + const isDarkTheme = useIsDarkTheme(); + return ( + <> + + + {iconElement} + {textElement} + + + + ); +} + +const ProFeatureTextContainer = styled.div` + display: flex; + flex-direction: column; + gap: var(--margins-xs); + align-items: flex-start; + text-align: start; +`; + +const ProFeatureTitle = styled.div` + display: inline-flex; + color: var(--text-primary-color); + font-size: var(--font-size-md); + font-weight: 700; +`; + +const ProFeatureDescription = styled.div` + font-size: 12px; // just because 13px does not look good + color: var(--text-secondary-color); +`; + +const StyledFeatureIcon = styled.div` + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 42px; + height: 42px; + padding: 0; + border-radius: var(--margins-xs); + color: var(--black-color); +`; + +type WithProFeaturePosition = { position: number }; + +function ProFeatureIconElement({ + unicode, + position, + noColor, +}: WithLucideUnicode & WithProFeaturePosition & { noColor?: boolean }) { + const bgStyle = + position === 0 + ? 'linear-gradient(135deg, #57C9FA 0%, #C993FF 100%)' + : position === 1 + ? 'linear-gradient(135deg, #C993FF 0%, #FF95EF 100%)' + : position === 2 + ? 'linear-gradient(135deg, #FF95EF 0%, #FF9C8E 100%)' + : position === 3 + ? 'linear-gradient(135deg, #FF9C8E 0%, #FCB159 100%)' + : position === 4 + ? 'linear-gradient(135deg, #FCB159 0%, #FAD657 100%)' + : 'none'; + + return ( + + + + + + ); +} + +function getProFeatures(userHasPro: boolean): Array< + { + dataTestId: SessionDataTestId; + id: + | 'proLongerMessages' + | 'proUnlimitedPins' + | 'proAnimatedDisplayPictures' + | 'proBadges' + | 'plusLoadsMore'; + title: TrArgs; + description: TrArgs; + } & WithLucideUnicode +> { + return [ + { + dataTestId: 'longer-messages-pro-settings-menu-item', + id: 'proLongerMessages', + title: { token: 'proLongerMessages' as const }, + description: { + token: userHasPro + ? ('proLongerMessagesDescription' as const) + : ('nonProLongerMessagesDescription' as const), + }, + unicode: LUCIDE_ICONS_UNICODE.MESSAGE_SQUARE, + }, + { + dataTestId: 'more-pins-pro-settings-menu-item', + id: 'proUnlimitedPins', + title: { token: 'proUnlimitedPins' as const }, + description: { + token: userHasPro + ? ('proUnlimitedPinsDescription' as const) + : ('nonProUnlimitedPinnedDescription' as const), + }, + unicode: LUCIDE_ICONS_UNICODE.PIN, + }, + { + dataTestId: 'animated-display-picture-pro-settings-menu-item', + id: 'proAnimatedDisplayPictures', + title: { token: 'proAnimatedDisplayPictures' as const }, + description: { token: 'proAnimatedDisplayPicturesDescription' as const }, + unicode: LUCIDE_ICONS_UNICODE.SQUARE_PLAY, + }, + { + dataTestId: 'badges-pro-settings-menu-item', + id: 'proBadges', + title: { token: 'proBadges' as const }, + description: { token: 'proBadgesDescription' as const }, + unicode: LUCIDE_ICONS_UNICODE.RECTANGLE_ELLIPSES, + }, + { + dataTestId: 'loads-more-pro-settings-menu-item', + id: 'plusLoadsMore', + title: { token: 'plusLoadsMore' as const }, + description: { + token: 'plusLoadsMoreDescription' as const, + icon: LUCIDE_ICONS_UNICODE.EXTERNAL_LINK_ICON, + }, + unicode: LUCIDE_ICONS_UNICODE.CIRCLE_PLUS, + }, + ]; +} + +function ProFeatures() { + const dispatch = useDispatch(); + const userHasPro = useCurrentUserHasPro(); + const expiredPro = useCurrentUserHasExpiredPro(); + const proFeatures = useMemo(() => getProFeatures(userHasPro), [userHasPro]); + + return ( + + + + {proFeatures.map((m, i) => { + return ( + { + showLinkVisitWarningDialog('https://getsession.org/pro-roadmap', dispatch); + } + : undefined + } + iconElement={ + + } + textElement={ + + + {m.id === 'proBadges' && ( + + )} + + + + + + + } + /> + ); + })} + + + ); +} + +function ManageProCurrentAccess({ returnToThisModalAction, centerAlign }: SectionProps) { + const dispatch = useDispatch(); + const { data } = useProAccessDetails(); + const userHasPro = useCurrentUserHasPro(); + if (!userHasPro) { + return null; + } + + return ( + + + + {data.autoRenew ? ( + { + dispatch( + userSettingsModal({ + userSettingsPage: 'proNonOriginating', + nonOriginatingVariant: 'cancel', + overrideBackAction: returnToThisModalAction, + centerAlign, + }) + ); + }} + color={'var(--danger-color)'} + iconElement={} + rowReverse + /> + ) : null} + { + dispatch( + userSettingsModal({ + userSettingsPage: 'proNonOriginating', + nonOriginatingVariant: 'refund', + overrideBackAction: returnToThisModalAction, + }) + ); + }} + color={'var(--danger-color)'} + iconElement={} + rowReverse + /> + + + ); +} + +// TODO: add logic to call libsession state +function useRecoverProStatus() { + const fetchAccess = useCallback(async () => { + await sleepFor(5000); + return { ok: false }; + }, []); + + return { fetchAccess }; +} + +function ManageProPreviousAccess({ returnToThisModalAction, centerAlign }: SectionProps) { + const dispatch = useDispatch(); + const isDarkTheme = useIsDarkTheme(); + const userHasExpiredPro = useCurrentUserHasExpiredPro(); + + const { isLoading, isError } = useProAccessDetails(); + const { fetchAccess: fetchRecoverAccess } = useRecoverProStatus(); + + const backendErrorButtons = useBackendErrorDialogButtons(); + + const handleClickRenew = useCallback(() => { + dispatch( + isError + ? updateLocalizedPopupDialog({ + title: { token: 'proStatusError' }, + description: { token: 'proStatusRenewError' }, + overrideButtons: backendErrorButtons, + }) + : isLoading + ? updateLocalizedPopupDialog({ + title: { token: 'proStatusLoading' }, + description: { token: 'checkingProStatusRenew' }, + }) + : userSettingsModal({ + userSettingsPage: 'proNonOriginating', + nonOriginatingVariant: 'renew', + overrideBackAction: returnToThisModalAction, + centerAlign, + }) + ); + }, [dispatch, isLoading, isError, backendErrorButtons, centerAlign, returnToThisModalAction]); + + const handleClickRecover = useCallback(async () => { + const result = await fetchRecoverAccess(); + + if (result.ok) { + return dispatch( + updateLocalizedPopupDialog({ + title: { token: 'proAccessRestored' }, + description: { token: 'proAccessRestoredDescription' }, + }) + ); + } + return dispatch( + updateLocalizedPopupDialog({ + title: { token: 'proAccessNotFound' }, + description: { token: 'proAccessNotFoundDescription' }, + overrideButtons: [ + { + label: { token: 'helpSupport' }, + dataTestId: 'pro-backend-error-support-button', + onClick: () => { + showLinkVisitWarningDialog( + 'https://sessionapp.zendesk.com/hc/sections/4416517450649-Support', + dispatch + ); + }, + closeAfterClick: true, + }, + { + label: { token: 'close' }, + dataTestId: 'modal-close-button', + closeAfterClick: true, + }, + ], + }) + ); + }, [dispatch, fetchRecoverAccess]); + + if (!userHasExpiredPro) { + return null; + } + + return ( + + + + + ) : ( + + ) + } + rowReverse + {...(isError || isLoading + ? { + subText: isError + ? { token: 'errorCheckingProStatus' } + : { token: 'checkingProStatusEllipsis' }, + subTextColorOverride: isError ? 'var(--warning-color)' : undefined, + } + : {})} + /> + void handleClickRecover()} + iconElement={} + rowReverse + /> + + + ); +} + +function ProHelp() { + const dispatch = useDispatch(); + return ( + + + + + showLinkVisitWarningDialog('https://getsession.org/faq#pro', dispatch) + } + /> + + showLinkVisitWarningDialog('https://getsession.org/pro-form', dispatch) + } + /> + + + ); +} + +function PageHero() { + const dispatch = useDispatch(); + const neverHadPro = useCurrentNeverHadPro(); + const proExpired = useCurrentUserHasExpiredPro(); + const { isLoading, isError } = useProAccessDetails(); + + const backendErrorButtons = useBackendErrorDialogButtons(); + + const handleClick = useCallback(() => { + if (isError) { + dispatch( + updateLocalizedPopupDialog({ + title: { token: 'proStatusError' }, + description: { token: 'proStatusNetworkErrorDescription' }, + overrideButtons: backendErrorButtons, + }) + ); + return; + } + + if (isLoading) { + dispatch( + updateLocalizedPopupDialog({ + title: { token: 'proStatusLoading' }, + description: { token: 'proStatusLoadingDescription' }, + }) + ); + } + // Do nothing if not error or loading + }, [dispatch, isLoading, isError, backendErrorButtons]); + + const heroStatusText = useMemo(() => { + if (isError) { + return ( +
+ + +
+ ); + } + if (isLoading) { + return ( + <> + + + + ); + } + return null; + }, [isLoading, isError, neverHadPro]); + + return ( + : null} + isError={isError} + noColors={proExpired} + /> + ); +} + +export function ProSettingsPage(modalState: { + userSettingsPage: 'pro'; + hideBackButton?: boolean; + hideHelp?: boolean; + centerAlign?: boolean; +}) { + const dispatch = useDispatch(); + const backAction = useUserSettingsBackAction(modalState); + const closeAction = useUserSettingsCloseAction(modalState); + + const returnToThisModalAction = useCallback(() => { + dispatch(userSettingsModal(modalState)); + }, [dispatch, modalState]); + + return ( + + ) : undefined + } + /> + } + onClose={closeAction || undefined} + centerAlign={modalState.centerAlign} + > + + + + + + + + {!modalState.hideHelp ? : null} + + + ); +} diff --git a/ts/components/dialog/user-settings/pages/userSettingsHooks.tsx b/ts/components/dialog/user-settings/pages/userSettingsHooks.tsx index 1ae07de49..62d6a353b 100644 --- a/ts/components/dialog/user-settings/pages/userSettingsHooks.tsx +++ b/ts/components/dialog/user-settings/pages/userSettingsHooks.tsx @@ -41,6 +41,9 @@ export function useUserSettingsTitle(page: UserSettingsModalState | undefined) { : page.passwordAction === 'change' ? tr('passwordChange') : tr('passwordSet'); + case 'pro': + case 'proNonOriginating': + return ''; case 'default': case undefined: return tr('sessionSettings'); @@ -72,6 +75,8 @@ export function useUserSettingsCloseAction(props: UserSettingsModalState) { case 'blocked-contacts': case 'password': case 'network': + case 'pro': + case 'proNonOriginating': return () => dispatch(userSettingsModal(null)); default: @@ -109,8 +114,12 @@ export function useUserSettingsBackAction(modalState: UserSettingsModalState) { case 'privacy': case 'preferences': case 'network': + case 'pro': settingsPageToDisplayOnBack = 'default'; break; + case 'proNonOriginating': + settingsPageToDisplayOnBack = 'pro'; + break; default: assertUnreachable(userSettingsPage, 'useBackActionFromPage: invalid userSettingsPage'); throw new Error('useBackActionFromPage: invalid userSettingsPage'); diff --git a/ts/components/icon/lucide.ts b/ts/components/icon/lucide.ts index 2adcb4942..b09f64667 100644 --- a/ts/components/icon/lucide.ts +++ b/ts/components/icon/lucide.ts @@ -12,6 +12,7 @@ export enum LUCIDE_ICONS_UNICODE { CHEVRON_LEFT = '', CHEVRON_RIGHT = '', CHEVRON_UP = '', + CIRCLE_ALERT = '', CIRCLE_CHECK = '', CIRCLE_ELLIPSES = '', CIRCLE_HELP = '', @@ -56,9 +57,11 @@ export enum LUCIDE_ICONS_UNICODE { REPLY = '', SEARCH = '', SETTINGS = '', + SMARTPHONE = '', SMILE_PLUS = '', SQUARE = '', SQUARE_CODE = '', + SQUARE_PLAY = '', SUN_MEDIUM = '', TIMER = '', TRASH2 = '', @@ -137,9 +140,11 @@ export function isIconToMirrorRtl(unicode: LUCIDE_ICONS_UNICODE) { case LUCIDE_ICONS_UNICODE.REPEAT_2: case LUCIDE_ICONS_UNICODE.SEARCH: case LUCIDE_ICONS_UNICODE.SETTINGS: + case LUCIDE_ICONS_UNICODE.SMARTPHONE: case LUCIDE_ICONS_UNICODE.SMILE_PLUS: case LUCIDE_ICONS_UNICODE.SQUARE: case LUCIDE_ICONS_UNICODE.SQUARE_CODE: + case LUCIDE_ICONS_UNICODE.SQUARE_PLAY: case LUCIDE_ICONS_UNICODE.SUN_MEDIUM: case LUCIDE_ICONS_UNICODE.TIMER: case LUCIDE_ICONS_UNICODE.TRASH2: @@ -151,6 +156,7 @@ export function isIconToMirrorRtl(unicode: LUCIDE_ICONS_UNICODE) { case LUCIDE_ICONS_UNICODE.USER_ROUND_X: case LUCIDE_ICONS_UNICODE.USERS_ROUND: case LUCIDE_ICONS_UNICODE.X: + case LUCIDE_ICONS_UNICODE.CIRCLE_ALERT: return false; default: assertUnreachable(unicode, 'isIconToMirrorRtl: unknown case'); diff --git a/ts/components/leftpane/ActionsPanel.tsx b/ts/components/leftpane/ActionsPanel.tsx index 883be17ef..64d243d80 100644 --- a/ts/components/leftpane/ActionsPanel.tsx +++ b/ts/components/leftpane/ActionsPanel.tsx @@ -58,6 +58,7 @@ import { GearAvatarButton } from '../buttons/avatar/GearAvatarButton'; import { useZoomShortcuts } from '../../hooks/useZoomingShortcut'; import { OnionStatusLight } from '../dialog/OnionStatusPathDialog'; import { AvatarReupload } from '../../session/utils/job_runners/jobs/AvatarReuploadJob'; +import { useDebugMenuModal } from '../../state/selectors/modal'; const StyledContainerAvatar = styled.div` padding: var(--margins-lg); @@ -162,12 +163,10 @@ function useUpdateBadgeCount() { function useDebugThemeSwitch() { useKey( (event: KeyboardEvent) => { - return event.ctrlKey && event.key === 't'; + return isDevProd() && event.ctrlKey && event.key === 't'; }, () => { - if (isDevProd()) { - void handleThemeSwitch(); - } + void handleThemeSwitch(); } ); } @@ -190,6 +189,32 @@ async function regenerateLastMessagesGroupsCommunities() { await Storage.put(SettingsKey.lastMessageGroupsRegenerated, true); } +function DebugMenuModalButton() { + const dispatch = useDispatch(); + const debugMenuModalState = useDebugMenuModal(); + + useKey( + (event: KeyboardEvent) => { + return isDevProd() && event.ctrlKey && event.key === 'd'; + }, + () => { + dispatch(updateDebugMenuModal(debugMenuModalState ? null : {})); + } + ); + + return ( + { + dispatch(updateDebugMenuModal({})); + }} + /> + ); +} + /** * ActionsPanel is the far left banner (not the left pane). * The panel with buttons to switch between the message/contact/settings/theme views @@ -286,17 +311,7 @@ export const ActionsPanel = () => { /> - {showDebugMenu && ( - { - dispatch(updateDebugMenuModal({})); - }} - /> - )} + {showDebugMenu ? : null} { dispatch(onionPathModal({})); diff --git a/ts/data/settings-key.ts b/ts/data/settings-key.ts index 57644e5de..f644d4979 100644 --- a/ts/data/settings-key.ts +++ b/ts/data/settings-key.ts @@ -15,6 +15,12 @@ const hasLinkPreviewPopupBeenDisplayed = 'hasLinkPreviewPopupBeenDisplayed'; const hasFollowSystemThemeEnabled = 'hasFollowSystemThemeEnabled'; const hideRecoveryPassword = 'hideRecoveryPassword'; +// Pro stats counters +const proLongerMessagesSent = 'proLongerMessagesSent'; +const proPinnedConversations = 'proPinnedConversations'; +const proBadgesSent = 'proBadgesSent'; +const proGroupsUpgraded = 'proGroupsUpgraded'; + // user config tracking timestamps (to discard incoming messages which would make a change we reverted in the last config message we merged) const latestUserProfileEnvelopeTimestamp = 'latestUserProfileEnvelopeTimestamp'; const latestUserGroupEnvelopeTimestamp = 'latestUserGroupEnvelopeTimestamp'; @@ -51,6 +57,10 @@ export const SettingsKey = { hideRecoveryPassword, showOnboardingAccountJustCreated, lastMessageGroupsRegenerated, + proLongerMessagesSent, + proPinnedConversations, + proBadgesSent, + proGroupsUpgraded, } as const; export const KNOWN_BLINDED_KEYS_ITEM = 'KNOWN_BLINDED_KEYS_ITEM'; diff --git a/ts/hooks/useHasPro.ts b/ts/hooks/useHasPro.ts index 01c6a8403..eba3d0f32 100644 --- a/ts/hooks/useHasPro.ts +++ b/ts/hooks/useHasPro.ts @@ -1,7 +1,21 @@ +import { useCallback, useMemo } from 'react'; import { UserUtils } from '../session/utils'; +import { + MockProAccessExpiryOptions, + useDataFeatureFlag, + useFeatureFlag, +} from '../state/ducks/types/releasedFeaturesReduxTypes'; +import { assertUnreachable } from '../types/sqlSharedTypes'; import { useIsProAvailable } from './useIsProAvailable'; import { useIsProUser } from './useParamSelector'; +import { + formatDateWithLocale, + formatRoundedUpTimeUntilTimestamp, +} from '../util/i18n/formatting/generics'; +/** + * Returns true if pro is available, and the current user has pro (active, not expired) + */ export function useCurrentUserHasPro() { const isProAvailable = useIsProAvailable(); const weArePro = useIsProUser(UserUtils.getOurPubKeyStrFromCache()); @@ -9,9 +23,232 @@ export function useCurrentUserHasPro() { return isProAvailable && weArePro; } +/** + * Returns true if pro is available, and the current user has expired pro. + */ +export function useCurrentUserHasExpiredPro() { + const isProAvailable = useIsProAvailable(); + // FIXME: we will need to have this coming from libsession (and stored in redux I guess) + + return isProAvailable && window.sessionFeatureFlags.mockCurrentUserHasProExpired; +} + +/** + * Returns true if pro is available, but the current user has never had pro. + * (i.e. the user does not have pro currently and doesn't have an expired pro either) + */ +export function useCurrentNeverHadPro() { + const isProAvailable = useIsProAvailable(); + const currentUserHasPro = useCurrentUserHasPro(); + const currentUserHasExpiredPro = useCurrentUserHasExpiredPro(); + return isProAvailable && !currentUserHasPro && !currentUserHasExpiredPro; +} + export function useUserHasPro(convoId?: string) { const isProAvailable = useIsProAvailable(); const userIsPro = useIsProUser(convoId); return isProAvailable && userIsPro; } + +// Mirrors backend enum +export enum ProAccessVariant { + Nil = 0, + OneMonth = 1, + ThreeMonth = 2, + TwelveMonth = 3, +} + +function proAccessVariantToString(variant: ProAccessVariant): string { + switch (variant) { + case ProAccessVariant.OneMonth: + return '1 Month'; + case ProAccessVariant.ThreeMonth: + return '3 Months'; + case ProAccessVariant.TwelveMonth: + return '12 Months'; + case ProAccessVariant.Nil: + return 'N/A'; + default: + return assertUnreachable(variant, `Unknown pro access variant: ${variant}`); + } +} + +// Mirrors backend enum +export enum ProOriginatingPlatform { + Nil = 0, + GooglePlayStore = 1, + iOSAppStore = 2, +} + +type OriginatingPlatformStrings = { + platform: string; + platform_store: string; + platform_account: string; + device_type: string; + platform_store_other: string; + platform_link_manage: string; + platform_link_cancel: string; + platform_link_refund: string; + session_support_link_refund: string; +}; + +// TODO: This should all be set by libsession +const platformStoreGoogle = 'Google Play Store'; +const platformStoreApple = 'Apple App Store'; +const refundLinkSessionSupport = 'https://getsession.org/android-refund'; + +function proAccessOriginatingPlatformToStrings( + platform: ProOriginatingPlatform +): OriginatingPlatformStrings { + switch (platform) { + case ProOriginatingPlatform.GooglePlayStore: + return { + platform: 'Google', + platform_account: 'Google account', + platform_store: platformStoreGoogle, + platform_store_other: platformStoreApple, + device_type: 'Android', + platform_link_manage: + 'https://play.google.com/store/account/subscriptions?package=network.loki.messenger', + // FIXME: set the sku dynamically if we dont use libsession + platform_link_cancel: + 'https://play.google.com/store/account/subscriptions?package=network.loki.messenger&sku=SESSION_PRO_MONTHLY', + platform_link_refund: 'https://support.google.com/googleplay/workflow/9813244?', + session_support_link_refund: refundLinkSessionSupport, + }; + case ProOriginatingPlatform.iOSAppStore: + return { + platform: 'Apple', + platform_account: 'Apple account', + platform_store: platformStoreApple, + platform_store_other: platformStoreGoogle, + device_type: 'iOS', + platform_link_manage: 'https://apps.apple.com/account/subscriptions', + platform_link_cancel: 'https://account.apple.com/account/manage/section/subscriptions', + platform_link_refund: 'https://support.apple.com/118223', + session_support_link_refund: refundLinkSessionSupport, + }; + case ProOriginatingPlatform.Nil: + return { + platform: '', + platform_account: '', + platform_store: platformStoreGoogle, + platform_store_other: platformStoreApple, + device_type: '', + platform_link_manage: '', + platform_link_refund: '', + platform_link_cancel: '', + session_support_link_refund: refundLinkSessionSupport, + }; + default: + return assertUnreachable(platform, `Unknown pro originating platform: ${platform}`); + } +} + +function useMockProAccessExpiry() { + const variant = useDataFeatureFlag('mockProAccessExpiry') ?? MockProAccessExpiryOptions.MONTH; + + // NOTE: for testing the expiry time should be pinned to x time after "now" + const now = variant ? Date.now() : 0; + switch (variant) { + case MockProAccessExpiryOptions.SOON: + return now + 600 * 1000; + case MockProAccessExpiryOptions.TODAY: + return now + 12 * 60 * 60 * 1000; + case MockProAccessExpiryOptions.TOMORROW: + return now + 26 * 60 * 60 * 1000; + case MockProAccessExpiryOptions.WEEK: + return now + 8 * 24 * 60 * 60 * 1000; + case MockProAccessExpiryOptions.MONTH: + return now + 30 * 24 * 60 * 60 * 1000; + case MockProAccessExpiryOptions.THREE_MONTH: + return now + 90 * 24 * 60 * 60 * 1000; + case MockProAccessExpiryOptions.YEAR: + return now + 12 * 30 * 24 * 60 * 60 * 1000; + case MockProAccessExpiryOptions.P24DT1M: + return now + 24 * 24 * 60 * 60 * 1000 + 60 * 60 * 1000; + case MockProAccessExpiryOptions.PT24H1M: + return now + 24 * 60 * 60 * 1000 + 60 * 60 * 1000; + case MockProAccessExpiryOptions.PT23H59M: + return now + 23 * 60 * 60 * 1000 + 59 * 60 * 1000; + case MockProAccessExpiryOptions.PT33M: + return now + 33 * 60 * 1000; + case MockProAccessExpiryOptions.PT1M: + return now + 1 * 60 * 1000; + case MockProAccessExpiryOptions.PT10S: + return now + 10 * 1000; + default: + return 0; + } +} + +// TODO: implement real deta fetching and move this to an appropriate place +export function useProAccessDetails() { + const mockIsLoading = useFeatureFlag('mockProBackendLoading'); + const mockIsError = useFeatureFlag('mockProBackendError'); + + // FIXME: These should not have defaults, but we need them for now as its the only way to get data + const mockVariant = useDataFeatureFlag('mockProAccessVariant') ?? ProAccessVariant.OneMonth; + const mockPlatform = + useDataFeatureFlag('mockProOriginatingPlatform') ?? ProOriginatingPlatform.GooglePlayStore; + const mockCancelled = useFeatureFlag('mockCurrentUserHasProCancelled') ?? false; + const mockInGracePeriod = useFeatureFlag('mockCurrentUserHasProInGracePeriod') ?? false; + const mockIsPlatformRefundAvailable = !useFeatureFlag( + 'mockCurrentUserHasProPlatformRefundExpired' + ); + const mockExpiry = useMockProAccessExpiry(); + + const isLoading = mockIsLoading; + + const data = useMemo(() => { + // FIXME: implement non-mock data fetching and parsing here + const variant = mockVariant; + const expiryTimeMs = mockExpiry; + const platform = mockPlatform; + return { + autoRenew: !mockCancelled, + inGracePeriod: mockInGracePeriod, + variant, + variantString: proAccessVariantToString(variant), + expiryTimeMs, + expiryTimeDateString: formatDateWithLocale({ + date: new Date(expiryTimeMs), + formatStr: 'MMM d, yyyy', + }), + expiryTimeRelativeString: formatRoundedUpTimeUntilTimestamp(expiryTimeMs), + isPlatformRefundAvailable: mockIsPlatformRefundAvailable, + platform, + platformStrings: proAccessOriginatingPlatformToStrings(platform), + }; + }, [ + mockVariant, + mockPlatform, + mockCancelled, + mockInGracePeriod, + mockIsPlatformRefundAvailable, + mockExpiry, + ]); + + const refetch = useCallback(() => { + if (isLoading) { + return; + } + if (mockIsError) { + window.sessionFeatureFlags.mockProBackendLoading = true; + window.sessionFeatureFlags.mockProBackendError = false; + setTimeout(() => { + window.sessionFeatureFlags.mockProBackendError = true; + window.sessionFeatureFlags.mockProBackendLoading = false; + }, 5000); + } + // TODO: Add non-mock pro backend refetch logic + }, [mockIsError, isLoading]); + + return { + isLoading, + isError: mockIsError, + refetch, + data, + }; +} diff --git a/ts/hooks/useIsProAvailable.ts b/ts/hooks/useIsProAvailable.ts index 1f1c4b9f4..83c135e75 100644 --- a/ts/hooks/useIsProAvailable.ts +++ b/ts/hooks/useIsProAvailable.ts @@ -3,3 +3,7 @@ import { useFeatureFlag } from '../state/ducks/types/releasedFeaturesReduxTypes' export function useIsProAvailable() { return !!useFeatureFlag('proAvailable'); } + +export function useIsProGroupsAvailable() { + return !!useFeatureFlag('proGroupsAvailable'); +} diff --git a/ts/react.d.ts b/ts/react.d.ts index a2a92a24d..6b65de395 100644 --- a/ts/react.d.ts +++ b/ts/react.d.ts @@ -95,12 +95,13 @@ declare module 'react' { | 'conversation-trimming' | 'auto-update' | 'auto-dark-mode' - | 'hide-menu-bar'; + | 'hide-menu-bar' + | 'pro-badge-visible'; type SettingsRadio = | `set-notifications-${'message' | 'name' | 'count'}` | `send-with-${'enterForSend' | 'enterForNewLine'}`; - type SettingsChevron = `blocked-contacts`; + type SettingsChevron = 'blocked-contacts' | 'update-access'; type SettingsInlineButtons = | 'set-password' @@ -108,7 +109,13 @@ declare module 'react' { | 'remove-password' | 'export-logs' | 'hide-recovery-password'; - type SettingsExternalLinkButtons = 'faq' | 'translate' | 'support' | 'feedback'; + type SettingsExternalLinkButtons = + | 'faq' + | 'translate' + | 'support' + | 'feedback' + | 'pro-faq' + | 'pro-support'; type SettingsMenuItems = | 'message-requests' @@ -125,6 +132,13 @@ declare module 'react' { | 'preferences' | 'donate'; + type ProFeatureItems = + | 'longer-messages' + | 'more-pins' + | 'animated-display-picture' + | 'badges' + | 'loads-more'; + type MenuItems = 'block' | 'delete' | 'accept'; type Inputs = @@ -189,6 +203,13 @@ declare module 'react' { | 'back' | 'modal-back' | 'create-group' + | 'cancel-pro' + | 'renew-pro' + | 'recover-pro' + | 'request-refund' + | 'pro-open-platform-website' + | 'pro-backend-error-retry' + | 'pro-backend-error-support' | `${ConfirmButtons}-confirm` | `${CancelButtons}-cancel` | `clear-${ClearButtons}` @@ -259,6 +280,9 @@ declare module 'react' { | `${SettingsMenuItems}-settings-menu-item` | `${Inputs}-input` + // Pro settings + | `${ProFeatureItems}-pro-settings-menu-item` + // timer options | DisappearTimeOptionDataTestId | DisappearOptionDataTestId diff --git a/ts/state/ducks/modalDialog.tsx b/ts/state/ducks/modalDialog.tsx index cb6523a4d..7d21295b9 100644 --- a/ts/state/ducks/modalDialog.tsx +++ b/ts/state/ducks/modalDialog.tsx @@ -1,14 +1,20 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { SessionDataTestId } from 'react'; import { BlockOrUnblockModalState } from '../../components/dialog/blockOrUnblock/BlockOrUnblockModalState'; import { EnterPasswordModalProps } from '../../components/dialog/EnterPasswordModal'; import { HideRecoveryPasswordDialogProps } from '../../components/dialog/HideRecoveryPasswordDialog'; import { SessionConfirmDialogProps } from '../../components/dialog/SessionConfirm'; import { MediaItemType } from '../../components/lightbox/LightboxGallery'; import { AttachmentTypeWithPath } from '../../types/Attachment'; -import type { EditProfilePictureModalProps, PasswordAction } from '../../types/ReduxTypes'; +import type { + EditProfilePictureModalProps, + PasswordAction, + ProNonOriginatingPageVariant, +} from '../../types/ReduxTypes'; import { WithConvoId } from '../../session/types/with'; import type { SessionProInfoVariant } from '../../components/dialog/SessionProInfoModal'; import type { TrArgs } from '../../localization/localeTools'; +import { SessionButtonType } from '../../components/basic/SessionButton'; export type BanType = 'ban' | 'unban'; @@ -25,13 +31,27 @@ export type UserSettingsPage = | 'clear-data' | 'password' | 'preferences' - | 'network'; + | 'network' + | 'pro' + | 'proNonOriginating'; export type WithUserSettingsPage = - | { userSettingsPage: Exclude } + | { userSettingsPage: Exclude } | { userSettingsPage: 'password'; passwordAction: PasswordAction; + } + | { + userSettingsPage: 'pro'; + hideBackButton?: boolean; + hideHelp?: boolean; + centerAlign?: boolean; + } + | { + userSettingsPage: 'proNonOriginating'; + nonOriginatingVariant: ProNonOriginatingPageVariant; + overrideBackAction?: () => void; + centerAlign?: boolean; }; export type ConfirmModalState = SessionConfirmDialogProps | null; @@ -53,10 +73,20 @@ export type OnionPathModalState = object | null; export type EnterPasswordModalState = EnterPasswordModalProps | null; export type DeleteAccountModalState = object | null; export type OpenUrlModalState = { urlToOpen: string } | null; + +export type LocalizedPopupDialogButtonOptions = { + label: TrArgs; + buttonType?: SessionButtonType; + dataTestId: SessionDataTestId; + onClick?: () => void; + closeAfterClick?: boolean; +}; export type LocalizedPopupDialogState = { title: TrArgs; description: TrArgs; + overrideButtons?: Array; } | null; + export type SessionProInfoState = { variant: SessionProInfoVariant } | null; export type UserProfileModalState = { diff --git a/ts/state/ducks/types/releasedFeaturesReduxTypes.ts b/ts/state/ducks/types/releasedFeaturesReduxTypes.ts index bb9a05d9d..7a002db48 100644 --- a/ts/state/ducks/types/releasedFeaturesReduxTypes.ts +++ b/ts/state/ducks/types/releasedFeaturesReduxTypes.ts @@ -1,3 +1,4 @@ +import { ProAccessVariant, ProOriginatingPlatform } from '../../../hooks/useHasPro'; import type { ProMessageFeature } from '../../../models/proMessageFeature'; import { DURATION } from '../../../session/constants'; @@ -13,17 +14,51 @@ export type SessionFeatureFlags = { showPopoverAnchors: boolean; debugInputCommands: boolean; proAvailable: boolean; + proGroupsAvailable: boolean; mockCurrentUserHasPro: boolean; + mockCurrentUserHasProExpired: boolean; + mockCurrentUserHasProPlatformRefundExpired: boolean; + mockCurrentUserHasProCancelled: boolean; + mockCurrentUserHasProInGracePeriod: boolean; + mockProRecoverButtonAlwaysSucceed: boolean; + mockProRecoverButtonAlwaysFail: boolean; mockOthersHavePro: boolean; mockMessageProFeatures: Array; + mockProBackendLoading: boolean; + mockProBackendError: boolean; fsTTL30s: boolean; }; +export enum MockProAccessExpiryOptions { + SOON = 0, + TODAY = 1, + TOMORROW = 2, + WEEK = 3, + MONTH = 4, + THREE_MONTH = 5, + YEAR = 6, + // The following are test cases from the PRD in ISO8601 duration format + P24DT1M = 7, + PT24H1M = 8, + PT23H59M = 9, + PT33M = 10, + PT1M = 11, + PT10S = 12, +} + +export type SessionFeatureFlagsWithData = { + mockProOriginatingPlatform: ProOriginatingPlatform | null; + mockProAccessVariant: ProAccessVariant | null; + mockProAccessExpiry: MockProAccessExpiryOptions | null; +}; + export type SessionFeatureFlagKeys = keyof SessionFeatureFlags; +export type SessionFeatureFlagWithDataKeys = keyof SessionFeatureFlagsWithData; /** * Check if the given flag is a Feature flag. * @note debug flags are not included in this check + * @note data flags are not included in this check */ export const isSessionFeatureFlag = (flag: unknown): flag is SessionFeatureFlagKeys => { const strFlag = flag as string; @@ -34,6 +69,22 @@ export const getFeatureFlag = (flag: T) => window.sessionFeatureFlags[flag]; export const useFeatureFlag = (flag: T) => getFeatureFlag(flag); +/** + * Check if the given flag is a Feature flag with data. + * @note debug flags are not included in this check + * @node boolean flags are not included in this check + */ +export const isSessionFeatureFlagWithData = ( + flag: unknown +): flag is SessionFeatureFlagWithDataKeys => { + const strFlag = flag as string; + return !strFlag.startsWith('debug') && Object.keys(window.sessionFeatureFlags).includes(strFlag); +}; +export const getDataFeatureFlag = (flag: T) => + window.sessionFeatureFlagsWithData[flag]; +export const useDataFeatureFlag = (flag: T) => + getDataFeatureFlag(flag); + export type SessionFlags = SessionFeatureFlags & { debugLogging: boolean; debugLibsessionDumps: boolean; diff --git a/ts/test/session/unit/utils/i18n/formatRoundedUpDuration_test.ts b/ts/test/session/unit/utils/i18n/formatRoundedUpDuration_test.ts new file mode 100644 index 000000000..f9d6bdae4 --- /dev/null +++ b/ts/test/session/unit/utils/i18n/formatRoundedUpDuration_test.ts @@ -0,0 +1,65 @@ +import { expect } from 'chai'; +import { formatRoundedUpDuration } from '../../../../../util/i18n/formatting/generics'; + +describe('formatRoundedUpDuration', () => { + it('<= 0 returns 1 minute', () => { + expect(formatRoundedUpDuration(-1)).to.equal('1 minute'); + expect(formatRoundedUpDuration(-1000)).to.equal('1 minute'); + expect(formatRoundedUpDuration(-3600000)).to.equal('1 minute'); + }); + + it('minutes only', () => { + expect(formatRoundedUpDuration(1000)).to.equal('1 minute'); + expect(formatRoundedUpDuration(59 * 1000)).to.equal('1 minute'); + expect(formatRoundedUpDuration(60 * 1000)).to.equal('1 minute'); + expect(formatRoundedUpDuration(2 * 60 * 1000)).to.equal('2 minutes'); + expect(formatRoundedUpDuration(59 * 60 * 1000)).to.equal('59 minutes'); + }); + + it('hours only', () => { + expect(formatRoundedUpDuration(60 * 60 * 1000)).to.equal('1 hour'); + expect(formatRoundedUpDuration(60 * 60 * 1000 + 1000)).to.equal('2 hours'); + expect(formatRoundedUpDuration(60 * 60 * 1000 + 59 * 1000)).to.equal('2 hours'); + expect(formatRoundedUpDuration(60 * 60 * 1000 + 60 * 1000)).to.equal('2 hours'); + expect(formatRoundedUpDuration(10 * 60 * 60 * 1000)).to.equal('10 hours'); + expect(formatRoundedUpDuration(23 * 60 * 60 * 1000 + 59 * 60 * 1000)).to.equal('24 hours'); + }); + + it('days only', () => { + expect(formatRoundedUpDuration(24 * 60 * 60 * 1000 + 1000)).to.equal('2 days'); + expect(formatRoundedUpDuration(24 * 60 * 60 * 1000 + 59 * 60 * 1000)).to.equal('2 days'); + expect(formatRoundedUpDuration(24 * 60 * 60 * 1000)).to.equal('1 day'); + expect(formatRoundedUpDuration(2 * 24 * 60 * 60 * 1000)).to.equal('2 days'); + expect(formatRoundedUpDuration(6 * 24 * 60 * 60 * 1000)).to.equal('6 days'); + }); + + it('edge cases - exactly at boundaries', () => { + // Exactly 1 minute + expect(formatRoundedUpDuration(60 * 1000)).to.equal('1 minute'); + // Exactly 1 hour + expect(formatRoundedUpDuration(60 * 60 * 1000)).to.equal('1 hour'); + // Exactly 1 day + expect(formatRoundedUpDuration(24 * 60 * 60 * 1000)).to.equal('1 day'); + // Just under 1 hour (59m 59s) + expect(formatRoundedUpDuration(59 * 60 * 1000 + 59 * 1000)).to.equal('60 minutes'); + // Just under 1 day (23h 59m 59s) + expect(formatRoundedUpDuration(23 * 60 * 60 * 1000 + 59 * 60 * 1000 + 59 * 1000)).to.equal( + '24 hours' + ); + }); + + it('rounding up examples from requirements', () => { + // exactly 24 Days and 1 Minute + expect(formatRoundedUpDuration(24 * 24 * 60 * 60 * 1000 + 60 * 1000)).to.equal('25 days'); + // exactly 24 hours and 1 minute + expect(formatRoundedUpDuration(24 * 60 * 60 * 1000 + 60 * 1000)).to.equal('2 days'); + // exactly 23 hours and 59 minutes + expect(formatRoundedUpDuration(23 * 60 * 60 * 1000 + 59 * 60 * 1000)).to.equal('24 hours'); + // exactly 33 minutes + expect(formatRoundedUpDuration(33 * 60 * 1000)).to.equal('33 minutes'); + // exactly 1 minute + expect(formatRoundedUpDuration(1 * 60 * 1000)).to.equal('1 minute'); + // exactly 10 seconds + expect(formatRoundedUpDuration(10 * 1000)).to.equal('1 minute'); + }); +}); diff --git a/ts/themes/classicDark.ts b/ts/themes/classicDark.ts index d1a6fe5b7..51ce83b7d 100644 --- a/ts/themes/classicDark.ts +++ b/ts/themes/classicDark.ts @@ -112,7 +112,7 @@ export const classicDark: ThemeColorVariables = { '--emoji-reaction-bar-icon-color': 'var(--text-primary-color)', '--modal-background-color': `rgba(${hexColorToRGB(COLORS.BLACK)}, 0.5)`, - '--modal-background-content-color': 'var(--background-primary-color)', + '--modal-background-content-color': COLORS.NEARBLACK, '--modal-text-color': 'var(--text-primary-color)', '--modal-text-danger-color': 'var(--danger-color)', '--modal-shadow-color': `rgba(${hexColorToRGB(COLORS.BLACK)}, 1.0)`, diff --git a/ts/themes/constants/colors.tsx b/ts/themes/constants/colors.tsx index 71359c264..c9ad1a5dd 100644 --- a/ts/themes/constants/colors.tsx +++ b/ts/themes/constants/colors.tsx @@ -25,6 +25,7 @@ export type ColorsType = { TRANSPARENT: string; WHITE: string; BLACK: string; + NEARBLACK: string; GREY: string; }; @@ -65,6 +66,7 @@ const white = '#FFF'; // Black const black = '#000'; +const nearBlack = '#0d0d0d'; // Grey const grey = '#616161'; @@ -88,6 +90,7 @@ const COLORS: ColorsType = { TRANSPARENT: transparent, WHITE: white, BLACK: black, + NEARBLACK: nearBlack, GREY: grey, }; diff --git a/ts/types/ReduxTypes.d.ts b/ts/types/ReduxTypes.d.ts index 3824cac06..076f76d97 100644 --- a/ts/types/ReduxTypes.d.ts +++ b/ts/types/ReduxTypes.d.ts @@ -7,6 +7,8 @@ export type PasswordAction = 'set' | 'change' | 'remove'; +export type ProNonOriginatingPageVariant = 'upgrade' | 'update' | 'cancel' | 'refund' | 'renew'; + export type EditProfilePictureModalProps = { conversationId: string; }; diff --git a/ts/util/i18n/formatting/generics.ts b/ts/util/i18n/formatting/generics.ts index 46a67469d..52b4c536f 100644 --- a/ts/util/i18n/formatting/generics.ts +++ b/ts/util/i18n/formatting/generics.ts @@ -4,6 +4,7 @@ import { format, formatDistanceStrict, formatDistanceToNowStrict, + formatDuration, formatRelative, } from 'date-fns'; import { omit, upperFirst } from 'lodash'; @@ -121,3 +122,45 @@ export const formatToTimeOnlyInEnglish = (date: Date) => { hour12: true, }).format(date); }; + +/** + * Formats a duration to a rounded up localized duration string. + * + * Rules: + * - Days: when more than 1 full day remains (rounds up) + * - Hours: when less than 1 full day but more than 1 full hour remains (rounds up) + * - Minutes: when less than 1 full hour remains (rounds up, minimum 1 minute) + * + * @param durationsMs - Duration in milliseconds + * @returns A formatted string like "25 days", "24 hours", or "33 minutes" + */ +export function formatRoundedUpDuration(durationMs: number): string { + const locale = getTimeLocaleDictionary(); + const daysRemaining = durationMs / (1000 * 60 * 60 * 24); + if (daysRemaining >= 1) { + const displayDays = Math.ceil(daysRemaining); + const duration = { days: displayDays }; + return formatDuration(duration, { locale }); + } + + const hoursRemaining = durationMs / (1000 * 60 * 60); + if (hoursRemaining >= 1) { + const displayHours = Math.ceil(hoursRemaining); + const duration = { hours: displayHours }; + return formatDuration(duration, { locale }); + } + + const minutesRemaining = durationMs / (1000 * 60); + const displayMinutes = Math.max(1, Math.ceil(minutesRemaining)); + const duration = { minutes: displayMinutes }; + return formatDuration(duration, { locale }); +} + +/** + * Formats the time remaining until a unix timestamp with localized duration strings. + * @see {@link formatRoundedUpDuration} + */ +export function formatRoundedUpTimeUntilTimestamp(unixTsMs: number): string { + const msRemaining = unixTsMs - Date.now(); + return formatRoundedUpDuration(msRemaining); +} diff --git a/ts/window.d.ts b/ts/window.d.ts index 883aeb817..9f4a1ebf1 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -6,7 +6,10 @@ import { Persistor } from 'redux-persist/es/types'; import { PrimaryColorStateType, ThemeStateType } from './themes/constants/colors'; import type { EventEmitter } from './shared/event_emitter'; -import type { SessionFlags } from './state/ducks/types/releasedFeaturesReduxTypes'; +import type { + SessionFeatureFlagsWithData, + SessionFlags, +} from './state/ducks/types/releasedFeaturesReduxTypes'; /* We declare window stuff here instead of global.d.ts because we are importing other declarations. @@ -23,6 +26,7 @@ declare global { setSettingValue: (id: string, value: any) => Promise; log: any; sessionFeatureFlags: SessionFlags; + sessionFeatureFlagsWithData: SessionFeatureFlagsWithData; onLogin: (pw: string) => Promise; // only set on the password window onTryPassword: (pw: string) => Promise; // only set on the main window persistStore?: Persistor;