1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-17 09:59:41 +00:00

Merge branch 'master' into feature/i18n-component-template

This commit is contained in:
Shane Melton
2023-10-10 07:47:04 -07:00
committed by GitHub
171 changed files with 3283 additions and 286 deletions

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "التعبئة التلقائية"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "إنشاء كلمة مرور (تم النسخ)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "لا توجد تسجيلات دخول مطابقة."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "افتح خزنتك"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Avto-doldurma"
},
"autoFillLogin": {
"message": "Giriş avto-doldurma"
},
"autoFillCard": {
"message": "Kart avto-doldurma"
},
"autoFillIdentity": {
"message": "Kimlik avto-doldurma"
},
"generatePasswordCopied": {
"message": "Parol yarat (kopyalandı)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Uyğun gələn hesab yoxdur."
},
"noCards": {
"message": "Kart yoxdur"
},
"noIdentities": {
"message": "Kimlik yoxdur"
},
"addLoginMenu": {
"message": "Giriş əlavə et"
},
"addCardMenu": {
"message": "Kart əlavə et"
},
"addIdentityMenu": {
"message": "Kimlik əlavə et"
},
"unlockVaultMenu": {
"message": "Anbarın kilidini açın"
},
@@ -671,7 +695,7 @@
"description": "'Solarized' is a noun and the name of a color scheme. It should not be translated."
},
"exportVault": {
"message": "Anbarı ixrac et"
"message": "Anbarı xaricə köçür"
},
"fileFormat": {
"message": "Fayl formatı"
@@ -681,19 +705,19 @@
"description": "WARNING (should stay in capitalized letters if the language permits)"
},
"confirmVaultExport": {
"message": "Anbarın ixracını təsdiqləyin"
"message": "Anbarın xaricə köçürülməsini təsdiqləyin"
},
"exportWarningDesc": {
"message": "Bu ixrac faylındakı anbar verilənləriniz şifrələnməmiş formatdadır. İxrac edilən faylı, güvənli olmayan kanallar üzərində saxlamamalı və ya göndərməməlisiniz (e-poçt kimi). Bu faylı işiniz bitdikdən sonra dərhal silin."
"message": "Xaricə köçürdüyünüz bu fayldakı datanız şifrələnməmiş formatdadır. Bu faylı güvənli olmayan kanallar (e-poçt kimi) üzərində saxlamamalı və ya göndərməməlisiniz. İşiniz bitdikdən sonra faylı dərhal silin."
},
"encExportKeyWarningDesc": {
"message": "Bu ixrac faylı, hesabınızın şifrələmə açarını istifadə edərək verilənlərinizi şifrələyir. Hesabınızın şifrələmə açarını döndərsəniz, bu ixrac faylının şifrəsini aça bilməyəcəyiniz üçün yenidən ixrac etməli olacaqsınız."
"message": "Xaricə köçürdüyünüz bu fayldakı data, hesabınızın şifrələmə açarı istifadə edilərək şifrələnir. Hesabınızın şifrələmə açarınıyişdirsəniz, bu faylın şifrəsini aça bilməyəcəksiniz və onu yenidən xaricə köçürməli olacaqsınız."
},
"encExportAccountWarningDesc": {
"message": "Hesab şifrələmə açarları, hər Bitwarden istifadəçi hesabı üçün unikaldır, buna görə də şifrələnmiş bir ixracı, fərqli bir hesaba idxal edə bilməzsiniz."
},
"exportMasterPassword": {
"message": "Anbar verilənlərinizi ixrac etmək üçün ana parolunuzu daxil edin."
"message": "Anbar datanızı xaricə köçürmək üçün ana parolunuzu daxil edin."
},
"shared": {
"message": "Paylaşılan"
@@ -799,7 +823,7 @@
"message": "YubiKey və Duo kimi mülkiyyətçi iki addımlı giriş seçimləri."
},
"ppremiumSignUpReports": {
"message": "Anbarınızın təhlükəsiyini təmin etmək üçün parol gigiyenası, hesab sağlamlığıverilənlərin pozulması hesabatları."
"message": "Anbarınızın təhlükəsizliyini təmin etmək üçün parol gigiyenası, hesab sağlamlığıdata pozuntusu hesabatları."
},
"ppremiumSignUpTotp": {
"message": "Anbarınızdakı hesablar üçün TOTP təsdiqləmə kodu (2FA) yaradıcısı."
@@ -2408,18 +2432,18 @@
"description": "Toggling an expand/collapse state."
},
"aliasDomain": {
"message": "Alias domain"
"message": "Domen ləqəbi"
},
"passwordRepromptDisabledAutofillOnPageLoad": {
"message": "Items with master password re-prompt cannot be auto-filled on page load. Auto-fill on page load turned off.",
"message": "\"Ana parolu təkrar soruş\" özəlliyi olan elementlər səhifə yüklənəndə avto-doldurulmur. \"Səhifə yüklənəndə avto-doldurma\" özəlliyi söndürülüb.",
"description": "Toast message for describing that master password re-prompt cannot be auto-filled on page load."
},
"autofillOnPageLoadSetToDefault": {
"message": "Auto-fill on page load set to use default setting.",
"message": "\"Səhifə yüklənəndə avto-doldurma\" özəlliyi ilkin tənzimləməni istifadə etmək üzrə tənzimləndi.",
"description": "Toast message for informing the user that auto-fill on page load has been set to the default setting."
},
"turnOffMasterPasswordPromptToEditField": {
"message": "Turn off master password re-prompt to edit this field",
"message": "Bu sahəyə düzəliş etmək üçün \"Ana parolu təkrar soruş\"u söndürün",
"description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item."
}
}

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Аўтазапаўненне"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Генерыраваць пароль (з капіяваннем)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Няма адпаведных лагінаў."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Разблакіраваць сховішча"
},

View File

@@ -14,7 +14,7 @@
"message": "Впишете се или създайте нов абонамент, за да достъпите защитен трезор."
},
"createAccount": {
"message": "Създаване на абонамент"
"message": "Създаване на акаунт"
},
"login": {
"message": "Вписване"
@@ -91,6 +91,15 @@
"autoFill": {
"message": "Автоматично дописване"
},
"autoFillLogin": {
"message": "Авт. попълване на данни за вход"
},
"autoFillCard": {
"message": "Самопопълваща се карта"
},
"autoFillIdentity": {
"message": "Самопопълваща се самоличност"
},
"generatePasswordCopied": {
"message": "Генериране на парола (копирана)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Няма съвпадащи записи."
},
"noCards": {
"message": "Няма карти"
},
"noIdentities": {
"message": "Няма самоличности"
},
"addLoginMenu": {
"message": "Добавяне на запис за вход"
},
"addCardMenu": {
"message": "Добавяне на карта"
},
"addIdentityMenu": {
"message": "Добавяне на самоличност"
},
"unlockVaultMenu": {
"message": "Отключете трезора си"
},
@@ -467,7 +491,7 @@
"message": "Грешен код за потвърждаване"
},
"valueCopied": {
"message": "$VALUE$ — копирано",
"message": "Копирано е $VALUE$",
"description": "Value has been copied to the clipboard.",
"placeholders": {
"value": {
@@ -537,7 +561,7 @@
"message": "Копирана парола"
},
"uri": {
"message": "Адрес"
"message": "Унифициран идентификатор на ресурс"
},
"uriPosition": {
"message": "Адрес $POSITION$",
@@ -550,7 +574,7 @@
}
},
"newUri": {
"message": "Нов адрес"
"message": "Нов унифициран идентификатор на ресурс"
},
"addedItem": {
"message": "Елементът е добавен"
@@ -631,7 +655,7 @@
"message": "Да се обнови ли паролата в Bitwarden?"
},
"notificationChangeSave": {
"message": "Да, нека се обнови сега"
"message": "Осъвременяване"
},
"notificationUnlockDesc": {
"message": "Отключете трезора си в Битуорден, за да завършите заявката за автоматично попълване."
@@ -684,7 +708,7 @@
"message": "Потвърждаване на изнасянето на трезора"
},
"exportWarningDesc": {
"message": "Данните от трезора ви ще се изнесат в незащитен формат. Не го пращайте по незащитени канали като е-поща. Изтрийте файла незабавно след като свършите работата си с него."
"message": "Този износ съдържа данни на трезора ви в некриптиран формат. Не трябва да съхранявате или изпращате износния файл през незащитени канали (като имейл). Изтрийте файла моментално след като свършите работата си с него."
},
"encExportKeyWarningDesc": {
"message": "При изнасяне данните се шифрират с ключа ви. Ако го смените, ще трябва наново да ги изнесете, защото няма да може да дешифрирате настоящия файл."
@@ -699,10 +723,10 @@
"message": "Споделено"
},
"learnOrg": {
"message": "Разберете повече за организациите"
"message": "Научете за организациите"
},
"learnOrgConfirmation": {
"message": "Битуорден позволява да споделяте части от трезора си чрез използването на организация. Искате ли да научите повече от сайта bitwarden.com?"
"message": "Битуорден позволява да споделяте елементи от трезора си а други, използвайки организация. Бихте ли посетили сайта bitwarden.com, за да научите повече?"
},
"moveToOrganization": {
"message": "Преместване в организация"
@@ -781,7 +805,7 @@
"message": "Управление на абонамента"
},
"premiumManageAlert": {
"message": "Можете да управлявате абонамента си през сайта bitwarden.com. Искате ли да го посетите сега?"
"message": "Може да управлявате членството си в мрежата на трезора в bitwarden.com. Искате ли да посетите уебсайта сега?"
},
"premiumRefresh": {
"message": "Опресняване на абонамента"
@@ -901,7 +925,7 @@
"message": "Регистрацията е защитена с двустепенно удостоверяване, но никой от настроените доставчици на удостоверяване не се поддържа от този браузър."
},
"noTwoStepProviders2": {
"message": "Пробвайте с поддържан уеб браузър (като Chrome или Firefox) и други доставчици на удостоверяване, които се поддържат от браузърите (като специални програми за удостоверяване)."
"message": "Употребявайте поддържан браузър (като Chrome, Firefox) и/или добавете други доставчици на удостоверяване, които се поддържат по-добре от браузърите (като специални програми за удостоверяване)."
},
"twoStepOptions": {
"message": "Настройки на двустепенното удостоверяване"
@@ -920,10 +944,10 @@
"description": "'Authy' and 'Google Authenticator' are product names and should not be translated."
},
"yubiKeyTitle": {
"message": "Устройство YubiKey OTP"
"message": "Ключ за сигурност YubiKey OTP"
},
"yubiKeyDesc": {
"message": "Използвайте устройство на YubiKey, за да влезете в абонамента си. Поддържат се моделите YubiKey 4, 4 Nano, 4C и NEO."
"message": "Използвайте ключа за сигурност YubiKey, за да влезете в акаунта си. Работи с устройствата YubiKey 4, 4 Nano, 4C и NEO."
},
"duoDesc": {
"message": "Удостоверяване чрез Duo Security, с ползване на приложението Duo Mobile, SMS, телефонен разговор или устройство U2F.",
@@ -2096,7 +2120,7 @@
"message": "собствен хостинг"
},
"thirdParty": {
"message": "Third-party"
"message": "Трета страна"
},
"thirdPartyServerMessage": {
"message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.",

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "স্বতঃপূরণ"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "পাসওয়ার্ড তৈরি করুন (অনুলিপিকৃত)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "কোনও মিলত লগইন নেই।"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-fill"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generate password (copied)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "No matching logins"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Emplenament automàtic"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Genera contrasenya (copiada)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "No hi ha inicis de sessió coincidents."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "1. Desbloquegeu la caixa forta."
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Automatické vyplňování"
},
"autoFillLogin": {
"message": "Automaticky vyplnit přihlášení"
},
"autoFillCard": {
"message": "Automaticky vyplnit kartu"
},
"autoFillIdentity": {
"message": "Automaticky vyplnit identitu"
},
"generatePasswordCopied": {
"message": "Vygenerovat heslo a zkopírovat do schránky"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Žádné odpovídající přihlašovací údaje"
},
"noCards": {
"message": "Žádné karty"
},
"noIdentities": {
"message": "Žádné identity"
},
"addLoginMenu": {
"message": "Přidat přihlašovací údaje"
},
"addCardMenu": {
"message": "Přidat kartu"
},
"addIdentityMenu": {
"message": "Přidat identitu"
},
"unlockVaultMenu": {
"message": "Odemknout Váš trezor"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Llenwi'n awtomatig"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Cynhyrchu cyfrinair (wedi'i gopïo)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "No matching logins"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Datgloi'ch cell"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-udfyld"
},
"autoFillLogin": {
"message": "Autoudfyld login"
},
"autoFillCard": {
"message": "Autoudfyld kort"
},
"autoFillIdentity": {
"message": "Autoudfyld identitet"
},
"generatePasswordCopied": {
"message": "Generér adgangskode (kopieret)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Ingen matchende logins"
},
"noCards": {
"message": "Ingen kort"
},
"noIdentities": {
"message": "Ingen identiteter"
},
"addLoginMenu": {
"message": "Tilføj login"
},
"addCardMenu": {
"message": "Tilføj kort"
},
"addIdentityMenu": {
"message": "Tilføj identitet"
},
"unlockVaultMenu": {
"message": "Lås din boks op"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-Ausfüllen"
},
"autoFillLogin": {
"message": "Zugangsdaten automatisch ausfüllen"
},
"autoFillCard": {
"message": "Karte automatisch ausfüllen"
},
"autoFillIdentity": {
"message": "Identität automatisch ausfüllen"
},
"generatePasswordCopied": {
"message": "Passwort generieren (kopiert)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Keine passenden Zugangsdaten"
},
"noCards": {
"message": "Keine Karten"
},
"noIdentities": {
"message": "Keine Identitäten"
},
"addLoginMenu": {
"message": "Zugangsdaten hinzufügen"
},
"addCardMenu": {
"message": "Karte hinzufügen"
},
"addIdentityMenu": {
"message": "Identität hinzufügen"
},
"unlockVaultMenu": {
"message": "Entsperre deinen Tresor"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Αυτόματη συμπλήρωση"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Δημιουργία Κωδικού (αντιγράφηκε)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Δεν υπάρχουν αντιστοιχίσεις σύνδεσης."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Ξεκλειδώστε το vault σας"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-fill"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generate password (copied)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "No matching logins"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-fill"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generate password (copied)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "No matching logins."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Autorellenar"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generar contraseña (copiada)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Sin entradas coincidentes."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Desbloquea la caja fuerte"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Automaatne täitmine"
},
"autoFillLogin": {
"message": "Täida konto andmed"
},
"autoFillCard": {
"message": "Täida kaardi andmed"
},
"autoFillIdentity": {
"message": "Täida identiteet"
},
"generatePasswordCopied": {
"message": "Genereeri parool (kopeeritakse)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Sobivaid kontoandmeid ei leitud."
},
"noCards": {
"message": "Kaardid puuduvad"
},
"noIdentities": {
"message": "Identiteedid puuduvad"
},
"addLoginMenu": {
"message": "Lisa konto andmed"
},
"addCardMenu": {
"message": "Lisa kaart"
},
"addIdentityMenu": {
"message": "Lisa identiteet"
},
"unlockVaultMenu": {
"message": "Lukusta hoidla lahti"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-betetzea"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Sortu pasahitza (kopiatuta)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Bat datozen saio-hasierarik gabe"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Desblokeatu kutxa gotorra"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "پر کردن خودکار"
},
"autoFillLogin": {
"message": "پر کردن خودکار ورود"
},
"autoFillCard": {
"message": "پر کردن خودکار کارت"
},
"autoFillIdentity": {
"message": "پر کردن خودکار هویت"
},
"generatePasswordCopied": {
"message": "ساخت کلمه عبور (کپی شد)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "ورودی‌ها منتطبق نیست"
},
"noCards": {
"message": "کارتی وجود ندارد"
},
"noIdentities": {
"message": "هویتی وجود ندارد"
},
"addLoginMenu": {
"message": "افزودن ورود"
},
"addCardMenu": {
"message": "افزودن کارت"
},
"addIdentityMenu": {
"message": "افزودن هویت"
},
"unlockVaultMenu": {
"message": "قفل گاوصندوق خود را باز کنید"
},
@@ -637,7 +661,7 @@
"message": "برای پر کردن خودکار گاوصندوق Bitwarden خود را باز کنید."
},
"notificationUnlock": {
"message": "بازگشایی"
"message": "باز کردن قفل"
},
"enableContextMenuItem": {
"message": "نمایش گزینه‌های منوی زمینه"
@@ -772,7 +796,7 @@
"message": "ویژگی موجود نیست"
},
"encryptionKeyMigrationRequired": {
"message": "Encryption key migration required. Please login through the web vault to update your encryption key."
"message": "انتقال کلید رمزگذاری مورد نیاز است. لطفاً از طریق گاوصندوق وب وارد شوید تا کلید رمزگذاری خود را به روز کنید."
},
"premiumMembership": {
"message": "عضویت پرمیوم"
@@ -1606,10 +1630,10 @@
"message": "بیومتریک مرورگر در این دستگاه پشتیبانی نمی‌شود."
},
"biometricsFailedTitle": {
"message": "زیست‌سنجی ناتمام ماند"
"message": "زیست‌سنجی ناموفق بود"
},
"biometricsFailedDesc": {
"message": "زیست‌سنجی نمی تواند انجام شود، استفاده از کلمه عبور اصلی یا خروج را در نظر بگیرید. اگر این مشکل ادامه یافت لطفا با پشتیبانی Bitwarden تماس بگیرید."
"message": "زیست‌سنجی نمیتواند انجام شود، استفاده از کلمه عبور اصلی یا خروج را در نظر بگیرید. اگر این مشکل ادامه یافت لطفاً با پشتیبانی Bitwarden تماس بگیرید."
},
"nativeMessaginPermissionErrorTitle": {
"message": "مجوز ارائه نشده است"
@@ -1992,7 +2016,7 @@
"message": "برون ریزی گاو‌صندوق شخصی"
},
"exportingIndividualVaultDescription": {
"message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated attachments.",
"message": "فقط موارد شخصی گاوصندوق مرتبط با $EMAIL$ برون ریزی خواهند شد. موارد گاوصندوق سازمان شامل نخواهد شد. فقط اطلاعات مورد گاوصندوق برون ریزی خواهد شد و شامل تاریخچه کلمه عبور مرتبط یا پیوست نمی‌شود.",
"placeholders": {
"email": {
"content": "$1",
@@ -2234,28 +2258,28 @@
}
},
"loggingInOn": {
"message": "ورود به عنوان"
"message": "ورود با"
},
"opensInANewWindow": {
"message": "در پنجره جدید باز می‌شود"
},
"deviceApprovalRequired": {
"message": ایید دستگاه لازم است. یک روش تایید برگزینید:"
"message": أیید دستگاه لازم است. یک روش تأیید انتخاب کنید:"
},
"rememberThisDevice": {
"message": "این دستگاه را به خاطر بسپار"
},
"uncheckIfPublicDevice": {
"message": "بردارید اگر از دستگاه عمومی استفاده میکنید"
"message": "اگر از دستگاه عمومی استفاده میکنید علامت را بردارید"
},
"approveFromYourOtherDevice": {
"message": ایید با دستگاه دیگرتان"
"message": أیید با دستگاه دیگرتان"
},
"requestAdminApproval": {
"message": "درخواست تایید مدیر"
"message": "درخواست تأیید مدیر"
},
"approveWithMasterPassword": {
"message": ایید با کلمه عبور اصلی"
"message": أیید با کلمه عبور اصلی"
},
"ssoIdentifierRequired": {
"message": "شناسه سازمان SSO مورد نیاز است."
@@ -2283,37 +2307,37 @@
"message": "حساب کاربری با موفقیت ایجاد شد!"
},
"adminApprovalRequested": {
"message": ایید مدیر در خواست شد"
"message": أیید مدیر درخواست شد"
},
"adminApprovalRequestSentToAdmins": {
"message": "درخواست شما به مدیرتان فرستاده شد."
},
"youWillBeNotifiedOnceApproved": {
"message": "به محض تایید مطلع خواهید شد."
"message": "به محض تأیید مطلع خواهید شد."
},
"troubleLoggingIn": {
"message": "در ورود مشکلی دارید؟"
},
"loginApproved": {
"message": "ورود تایید شد"
"message": "ورود تأیید شد"
},
"userEmailMissing": {
"message": "رایانامه کاربر کم است"
"message": "ایمیل کاربر وجود ندارد"
},
"deviceTrusted": {
"message": "دستگاه مورد اعتماد است"
},
"inputRequired": {
"message": "ورودی مورد نیاز است."
"message": "ورودی ضروری است."
},
"required": {
"message": "الزامی"
"message": "ضروری"
},
"search": {
"message": "جستجو"
},
"inputMinLength": {
"message": "ورودی باید حداقل $COUNT$ نشانه داشته باشد.",
"message": "ورودی باید حداقل $COUNT$ کاراکتر داشته باشد.",
"placeholders": {
"count": {
"content": "$1",
@@ -2322,7 +2346,7 @@
}
},
"inputMaxLength": {
"message": "اندازه ورودی نباید بیش از $COUNT$ نشانه باشد.",
"message": "طول ورودی نباید بیش از $COUNT$ کاراکتر باشد.",
"placeholders": {
"count": {
"content": "$1",
@@ -2331,7 +2355,7 @@
}
},
"inputForbiddenCharacters": {
"message": "نشانه های زیر مجاز نیستند: $CHARACTERS$",
"message": "کاراکترهای زیر مجاز نیستند: $CHARACTERS$",
"placeholders": {
"characters": {
"content": "$1",
@@ -2340,7 +2364,7 @@
}
},
"inputMinValue": {
"message": "مقدار ورودی باید دست کم $MIN$ باشد.",
"message": "مقدار ورودی باید حداقل $MIN$ باشد.",
"placeholders": {
"min": {
"content": "$1",
@@ -2349,7 +2373,7 @@
}
},
"inputMaxValue": {
"message": "مقدار ورودی نباید بیش از $MAX$ باشد.",
"message": "مقدار ورودی نباید از $MAX$ تجاوز کند.",
"placeholders": {
"max": {
"content": "$1",
@@ -2358,17 +2382,17 @@
}
},
"multipleInputEmails": {
"message": "یک یا چند رایانامه نامعتبر است"
"message": "یک یا چند ایمیل نامعتبر است"
},
"inputTrimValidator": {
"message": "ورودی نباید فقط فاصله باشد.",
"message": "ورودی نباید فقط حاوی فضای خالی باشد.",
"description": "Notification to inform the user that a form's input can't contain only whitespace."
},
"inputEmail": {
"message": "ورودی یک نشانی رایانامه نیست."
"message": "ورودی یک نشانی ایمیل نیست."
},
"fieldsNeedAttention": {
"message": "بخش (های) $COUNT$ در بالا نیازمند توجه شما است.",
"message": "فیلد $COUNT$ در بالا به توجه شما نیاز دارد.",
"placeholders": {
"count": {
"content": "$1",
@@ -2380,16 +2404,16 @@
"message": "-- انتخاب --"
},
"multiSelectPlaceholder": {
"message": "-- برای گزینش چیزی بنویسید --"
"message": "-- برای فیلتر تایپ کنید --"
},
"multiSelectLoading": {
"message": "در حال بازیابی گزینه‌ها..."
},
"multiSelectNotFound": {
"message": "موردی پیدا نشد"
"message": "موردی یافت نشد"
},
"multiSelectClearAll": {
"message": "پاک کردن همه"
"message": "پاککردن همه"
},
"plusNMore": {
"message": "+ $QUANTITY$ بیشتر",
@@ -2401,25 +2425,25 @@
}
},
"submenu": {
"message": "زیرفهرست"
"message": "زیرمنو"
},
"toggleCollapse": {
"message": "باز و بسته کردن",
"message": "دکمه بستن",
"description": "Toggling an expand/collapse state."
},
"aliasDomain": {
"message": "Alias domain"
"message": "دامنه مستعار"
},
"passwordRepromptDisabledAutofillOnPageLoad": {
"message": "Items with master password re-prompt cannot be auto-filled on page load. Auto-fill on page load turned off.",
"message": "موارد با درخواست مجدد کلمه عبور اصلی را نمی‌توان در بارگذاری صفحه به‌صورت خودکار پر کرد. پر کردن خودکار در بارگیری صفحه خاموش شد.",
"description": "Toast message for describing that master password re-prompt cannot be auto-filled on page load."
},
"autofillOnPageLoadSetToDefault": {
"message": "Auto-fill on page load set to use default setting.",
"message": "پر کردن خودکار در بارگیری صفحه برای استفاده از تنظیمات پیش‌فرض تنظیم شده است.",
"description": "Toast message for informing the user that auto-fill on page load has been set to the default setting."
},
"turnOffMasterPasswordPromptToEditField": {
"message": "Turn off master password re-prompt to edit this field",
"message": "برای ویرایش این فیلد، درخواست مجدد کلمه عبور اصلی را خاموش کنید",
"description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item."
}
}

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Automaattinen täyttö"
},
"autoFillLogin": {
"message": "Täytä kirjautumistieto automaattisesti"
},
"autoFillCard": {
"message": "Täytä kortti automaattisesti"
},
"autoFillIdentity": {
"message": "Täytä identiteetti automaattisesti"
},
"generatePasswordCopied": {
"message": "Luo salasana (leikepöydälle)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Ei tunnistettuja kirjautumistietoja."
},
"noCards": {
"message": "Kortteja ei ole"
},
"noIdentities": {
"message": "Identiteettejä ei ole"
},
"addLoginMenu": {
"message": "Lisää kirjautumistieto"
},
"addCardMenu": {
"message": "Lisää kortti"
},
"addIdentityMenu": {
"message": "Lisää identiteetti"
},
"unlockVaultMenu": {
"message": "Avaa holvisi"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-fill sa Filipino ay Awtomatikong Pagpuno"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Maglagay ng Password"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Walang tumutugmang mga login"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Buksan ang iyong kahadeyero"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Saisie automatique"
},
"autoFillLogin": {
"message": "Saisie automatique de l'identifiant"
},
"autoFillCard": {
"message": "Saisie automatique de la carte"
},
"autoFillIdentity": {
"message": "Saisie automatique de l'identité"
},
"generatePasswordCopied": {
"message": "Générer un mot de passe (copié)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Aucun identifiant correspondant."
},
"noCards": {
"message": "Aucune carte"
},
"noIdentities": {
"message": "Aucune identité"
},
"addLoginMenu": {
"message": "Ajouter un identifiant"
},
"addCardMenu": {
"message": "Ajouter une carte"
},
"addIdentityMenu": {
"message": "Ajouter une identité"
},
"unlockVaultMenu": {
"message": "Déverrouillez votre coffre"
},
@@ -634,7 +658,7 @@
"message": "Mettre à jour"
},
"notificationUnlockDesc": {
"message": "Unlock your Bitwarden vault to complete the auto-fill request."
"message": "Déverrouillez votre coffre Bitwarden pour terminer la demande de saisie automatique."
},
"notificationUnlock": {
"message": "Déverrouiller"

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-fill"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generate password (copied)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "No matching logins"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "השלמה אוטומטית"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "צור סיסמה (העתק)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "לא נמצאו פרטי כניסה תואמים."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "שחרור הכספת שלך"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "स्वत:भरण"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generate Password (copied)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "कोई मेल-मिला लॉगिन नहीं |"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "आपकी तिजोरी का ताला खोलें"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-ispuna"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generiraj lozinku (i kopiraj)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Nema podudarajućih prijava"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Otključaj svoj trezor"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Automatikus kitöltés"
},
"autoFillLogin": {
"message": "Automatikus kitöltés bejelentkezés"
},
"autoFillCard": {
"message": "Automatikus kitöltés kártya"
},
"autoFillIdentity": {
"message": "Automatikus kitöltés személyazonosság"
},
"generatePasswordCopied": {
"message": "Jelszó generálás (másolt)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Nincsenek egyező bejelentkezések."
},
"noCards": {
"message": "Nincsenek kártyák"
},
"noIdentities": {
"message": "Nincsenek személyazonosságok"
},
"addLoginMenu": {
"message": "Bejelentkezés hozzáadása"
},
"addCardMenu": {
"message": "Kártya hozzáadása"
},
"addIdentityMenu": {
"message": "Személyazonossság hozzáadása"
},
"unlockVaultMenu": {
"message": "Széf kinyitása"
},
@@ -143,7 +167,7 @@
"message": "A folytatáshoz meg kell erősíteni a személyazonosságot."
},
"account": {
"message": "Felhasználó"
"message": "Fiók"
},
"changeMasterPassword": {
"message": "Mesterjelszó módosítása"
@@ -513,7 +537,7 @@
"message": "A kétlépcsős bejelentkezés biztonságosabbá teszi a fiókot azáltal, hogy ellenőrizni kell a bejelentkezést egy másik olyan eszközzel mint például biztonsági kulcs, hitelesítő alkalmazás, SMS, telefon hívás vagy email. A kétlépcsős bejelentkezést a bitwarden.com webes széfben lehet engedélyezni. Felkeressük a webhelyet most?"
},
"editedFolder": {
"message": "A mappa módosításra került."
"message": "A mappa mentésre került."
},
"deleteFolderConfirmation": {
"message": "Biztos, hogy törölni akarod ezt a mappát?"
@@ -562,7 +586,7 @@
"message": "Biztosan törlésre kerüljön ezt az elem?"
},
"deletedItem": {
"message": "Az elem törlésre került."
"message": "Az elem a lomtárba került."
},
"overwritePassword": {
"message": "Jelszó felülírása"
@@ -769,7 +793,7 @@
"message": "A naximális fájlméret 500 MB."
},
"featureUnavailable": {
"message": "Ez a funkció nem érhető el."
"message": "A funkció nem érhető el."
},
"encryptionKeyMigrationRequired": {
"message": "Titkosítási kulcs migráció szükséges. Jelentkezzünk be a webes széfen keresztül a titkosítási kulcs frissítéséhez."
@@ -787,7 +811,7 @@
"message": "Tagság frissítése"
},
"premiumNotCurrentMember": {
"message": "Jelenleg nincs prémium tagság."
"message": "Jelenleg nem vagyunk prémium tag."
},
"premiumSignUpAndGet": {
"message": "Regisztráció a prémium tagságra az alábbi funkciókért:"
@@ -796,7 +820,7 @@
"message": "1 GB titkosított tárhely a fájlmellékleteknek."
},
"premiumSignUpTwoStepOptions": {
"message": "Proprietary two-step login options such as YubiKey and Duo."
"message": "Saját kétlépcsős bejelentkezési lehetőségek mint a YubiKey és a Duo."
},
"ppremiumSignUpReports": {
"message": "Jelszó higiénia, fiók biztonság és adatszivárgási jelentések a széf biztonsága érdekében."
@@ -817,7 +841,7 @@
"message": "A prémium tagság megvásárolható a bitwarden.com webes széfben. Szeretnénk felkeresni a webhelyet most?"
},
"premiumCurrentMember": {
"message": "Jelenleg a prémium tagság érvényben van."
"message": "Prémium tag vagyunk!"
},
"premiumCurrentMemberThanks": {
"message": "Köszönjük a Bitwarden támogatását."
@@ -994,7 +1018,7 @@
"message": "Alapértelmezett beállítások bejelentkezési elemekhez"
},
"defaultAutoFillOnPageLoadDesc": {
"message": "Az Automatikus kitöltés engedélyezése az oldalbetöltéskor engedélyezheti vagy letilthatja a funkciót az egyes bejelentkezési elemeknél. Ez az alapértelmezett beállítás a bejelentkezési elemeknéll, amelyek nincsenek külön konfigurálva."
"message": "Az egyes bejelentkezési elemeknél kikapcsolhatjuk oldalbetöltéskor az automatikus kitöltést az elem Szerkesztés nézetében."
},
"itemAutoFillOnPageLoad": {
"message": "Automatikus kitöltés oldal betöltésnél (Ha engedélyezett az opcióknál)"
@@ -1905,7 +1929,7 @@
"message": "Perc"
},
"vaultTimeoutPolicyInEffect": {
"message": "A szervezeti házirendek hatással vannak a széf időkorlátjára. A széf időkorlátja legfeljebb $HOURS$ óra és $MINUTES$ perc lehet.",
"message": "A szervezeti szabályzata $HOURS$ óra és $MINUTES$ percre állította be a maximálisan megengedett széf időtúllépést.",
"placeholders": {
"hours": {
"content": "$1",

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Isi otomatis"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Membuat Kata Sandi (tersalin)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Tidak ada info masuk yang cocok."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Buka brankas Anda"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Riempimento automatico"
},
"autoFillLogin": {
"message": "Riempi automaticamente login"
},
"autoFillCard": {
"message": "Riempi automaticamente carta"
},
"autoFillIdentity": {
"message": "Riempi automaticamente identità"
},
"generatePasswordCopied": {
"message": "Genera password e copiala"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Nessun login corrispondente"
},
"noCards": {
"message": "Nessuna carta"
},
"noIdentities": {
"message": "Nessuna identità"
},
"addLoginMenu": {
"message": "Aggiungi login"
},
"addCardMenu": {
"message": "Aggiungi carta"
},
"addIdentityMenu": {
"message": "Aggiungi identità"
},
"unlockVaultMenu": {
"message": "Sblocca la tua cassaforte"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "自動入力"
},
"autoFillLogin": {
"message": "自動入力ログイン"
},
"autoFillCard": {
"message": "自動入力カード"
},
"autoFillIdentity": {
"message": "自動入力 ID"
},
"generatePasswordCopied": {
"message": "パスワードを生成 (コピー)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "一致するログインがありません。"
},
"noCards": {
"message": "カードなし"
},
"noIdentities": {
"message": "ID なし"
},
"addLoginMenu": {
"message": "ログイン情報を追加"
},
"addCardMenu": {
"message": "カードを追加"
},
"addIdentityMenu": {
"message": "ID を追加"
},
"unlockVaultMenu": {
"message": "保管庫のロックを解除"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "თვითშევსება"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generate password (copied)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "No matching logins"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-fill"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generate password (copied)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "No matching logins"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "ಸ್ವಯಂ ಭರ್ತಿ"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "ಪಾಸ್ವರ್ಡ್ ರಚಿಸಿ (ನಕಲಿಸಲಾಗಿದೆ)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "ಹೊಂದಾಣಿಕೆಯ ಲಾಗಿನ್‌ಗಳು ಇಲ್ಲ."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "자동 완성"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "비밀번호 생성 및 클립보드에 복사"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "사용할 수 있는 로그인이 없습니다."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "보관함 잠금 해제"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Automatinis užpildymas"
},
"autoFillLogin": {
"message": "Automatinio užpildymo prisijungimas"
},
"autoFillCard": {
"message": "Automatinio užpildymo kortelė"
},
"autoFillIdentity": {
"message": "Automatinio užpildymo tapatybė"
},
"generatePasswordCopied": {
"message": "Kurti slaptažodį (paruoštas įterpti)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Nėra atitinkančių prisijungimų."
},
"noCards": {
"message": "Nėra kortelių"
},
"noIdentities": {
"message": "Nėra tapatybių"
},
"addLoginMenu": {
"message": "Pridėti prisijungimą"
},
"addCardMenu": {
"message": "Pridėti kortelę"
},
"addIdentityMenu": {
"message": "Pridėti tapatybę"
},
"unlockVaultMenu": {
"message": "Atrakinti saugyklą"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Automātiskā aizpildīšana"
},
"autoFillLogin": {
"message": "Automātiski aizpildīt pieteikšanos"
},
"autoFillCard": {
"message": "Automātiski aizpildīt karti"
},
"autoFillIdentity": {
"message": "Automātiski aizpildīt identitāti"
},
"generatePasswordCopied": {
"message": "Izveidot paroli (tiks ievietota starpliktuvē)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Nav atbilstošu pieteikšanās vienumu"
},
"noCards": {
"message": "Nav karšu"
},
"noIdentities": {
"message": "Nav identitāšu"
},
"addLoginMenu": {
"message": "Pievienot pieteikšanās vienumu"
},
"addCardMenu": {
"message": "Pievienot karti"
},
"addIdentityMenu": {
"message": "Pievienot identitāti"
},
"unlockVaultMenu": {
"message": "Atslēgt glabātavu"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "ഓട്ടോഫിൽ"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "പാസ്‌വേഡ് സൃഷ്ടിക്കുക (പകർത്തുക )"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "പൊരുത്തപ്പെടുന്ന ലോഗിനുകളൊന്നുമില്ല."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "स्वयंभरण"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generate password (copied)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "No matching logins"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "तिजोरी उघडा"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-fill"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generate password (copied)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "No matching logins"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-utfylling"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generer et passord (kopiert)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Ingen samsvarende innlogginger."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Lås opp hvelvet ditt"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-fill"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generate password (copied)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "No matching logins"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-invullen"
},
"autoFillLogin": {
"message": "Login automatisch invullen"
},
"autoFillCard": {
"message": "Kaart automatisch invullen"
},
"autoFillIdentity": {
"message": "Identiteit automatisch invullen"
},
"generatePasswordCopied": {
"message": "Wachtwoord genereren (op klembord)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Geen overeenkomstige logins."
},
"noCards": {
"message": "Geen kaarten"
},
"noIdentities": {
"message": "Geen identiteiten"
},
"addLoginMenu": {
"message": "Login toevoegen"
},
"addCardMenu": {
"message": "Kaart toevoegen"
},
"addIdentityMenu": {
"message": "Identiteit toevoegen"
},
"unlockVaultMenu": {
"message": "Ontgrendel je kluis"
},
@@ -772,7 +796,7 @@
"message": "Functionaliteit niet beschikbaar"
},
"encryptionKeyMigrationRequired": {
"message": "Encryption key migration required. Please login through the web vault to update your encryption key."
"message": "Migratie van de encryptiesleutel vereist. Login via de website om je sleutel te bij te werken."
},
"premiumMembership": {
"message": "Premium-abonnement"

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-fill"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generate password (copied)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "No matching logins"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-fill"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generate password (copied)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "No matching logins"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Autouzupełnianie"
},
"autoFillLogin": {
"message": "Autouzupełnianie logowania"
},
"autoFillCard": {
"message": "Autouzupełnianie karty"
},
"autoFillIdentity": {
"message": "Autouzupełnianie tożsamości"
},
"generatePasswordCopied": {
"message": "Wygeneruj hasło (do schowka)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Brak pasujących danych logowania"
},
"noCards": {
"message": "Brak kart"
},
"noIdentities": {
"message": "Brak tożsamości"
},
"addLoginMenu": {
"message": "Dodaj dane logowania"
},
"addCardMenu": {
"message": "Dodaj kartę"
},
"addIdentityMenu": {
"message": "Dodaj tożsamość"
},
"unlockVaultMenu": {
"message": "Odblokuj sejf"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Autopreencher"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Gerar Senha (copiada)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Sem credenciais correspondentes."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Desbloqueie seu cofre"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Preenchimento automático"
},
"autoFillLogin": {
"message": "Preenchimento automático da credencial"
},
"autoFillCard": {
"message": "Preenchimento automático do cartão"
},
"autoFillIdentity": {
"message": "Preenchimento automático da identidade"
},
"generatePasswordCopied": {
"message": "Gerar palavra-passe (copiada)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Sem credenciais correspondentes"
},
"noCards": {
"message": "Sem cartões"
},
"noIdentities": {
"message": "Sem identidades"
},
"addLoginMenu": {
"message": "Adicionar credencial"
},
"addCardMenu": {
"message": "Adicionar cartão"
},
"addIdentityMenu": {
"message": "Adicionar identidade"
},
"unlockVaultMenu": {
"message": "Desbloquear o cofre"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-completare"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generare parolă (s-a copiat)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Nu există potrivire de autentificări"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Deblocați-vă seiful"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Автозаполнение"
},
"autoFillLogin": {
"message": "Автозаполнение логина"
},
"autoFillCard": {
"message": "Автозаполнение карты"
},
"autoFillIdentity": {
"message": "Автозаполнение личности"
},
"generatePasswordCopied": {
"message": "Сгенерировать пароль (с копированием)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Нет подходящих логинов."
},
"noCards": {
"message": "Нет карт"
},
"noIdentities": {
"message": "Нет личностей"
},
"addLoginMenu": {
"message": "Добавить логин"
},
"addCardMenu": {
"message": "Добавить карту"
},
"addIdentityMenu": {
"message": "Добавить личность"
},
"unlockVaultMenu": {
"message": "Разблокировать хранилище"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "ස්වයං-පිරවීම"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "මුරපදය ජනනය (පිටපත්)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "ගැලපෙන පිවිසුම් නොමැත."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Automatické vypĺňanie"
},
"autoFillLogin": {
"message": "Automatické vyplnenie prihlasovacích údajov"
},
"autoFillCard": {
"message": "Automatické vyplnenie karty"
},
"autoFillIdentity": {
"message": "Automatické vyplnenie identity"
},
"generatePasswordCopied": {
"message": "Vygenerovať heslo (skopírované)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Žiadne zodpovedajúce prihlasovacie údaje."
},
"noCards": {
"message": "Žiadne karty"
},
"noIdentities": {
"message": "Žiadne identity"
},
"addLoginMenu": {
"message": "Pridať prihlasovacie údaje"
},
"addCardMenu": {
"message": "Pridať kartu"
},
"addIdentityMenu": {
"message": "Pridať identitu"
},
"unlockVaultMenu": {
"message": "Odomknúť trezor"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Samodejno izpolnjevanje"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generiraj geslo (kopirano)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Ni ustreznih prijav."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Odkleni svoj trezor"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Аутоматско допуњавање"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Генериши Лозинку (копирано)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Нема одговарајућих пријављивања."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Откључај свој сеф"
},
@@ -2408,7 +2432,7 @@
"description": "Toggling an expand/collapse state."
},
"aliasDomain": {
"message": "Alias domain"
"message": "Домен алијаса"
},
"passwordRepromptDisabledAutofillOnPageLoad": {
"message": "Ставке са упитом за поновно постављање главне лозинке не могу се ауто-попунити при учитавању странице. Ауто-попуњавање при учитавању странице је искључено.",

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Fyll i automatiskt"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Skapa lösenord (kopierad)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Inga matchande inloggningar"
},
"noCards": {
"message": "Inga kort"
},
"noIdentities": {
"message": "Inga identiteter"
},
"addLoginMenu": {
"message": "Lägg till inloggning"
},
"addCardMenu": {
"message": "Lägg till kort"
},
"addIdentityMenu": {
"message": "Lägg till identitet"
},
"unlockVaultMenu": {
"message": "Lås upp ditt valv"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Auto-fill"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generate password (copied)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "No matching logins"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Unlock your vault"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "กรอกข้อมูลอัตโนมัติ"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Generate Password (copied)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "ไม่พบข้อมูลล็อกอินที่ตรงกัน"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "ปลดล็อกกตู้นิรภัยของคุณ"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Otomatik doldur"
},
"autoFillLogin": {
"message": "Hesabı otomatik doldur"
},
"autoFillCard": {
"message": "Kartı otomatik doldur"
},
"autoFillIdentity": {
"message": "Kimliği otomatik doldur"
},
"generatePasswordCopied": {
"message": "Parola oluştur (ve kopyala)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Eşleşen hesap yok"
},
"noCards": {
"message": "Kart yok"
},
"noIdentities": {
"message": "Kimlik yok"
},
"addLoginMenu": {
"message": "Hesap ekle"
},
"addCardMenu": {
"message": "Kart ekle"
},
"addIdentityMenu": {
"message": "Kimlik ekle"
},
"unlockVaultMenu": {
"message": "Kasanızın kilidini açın"
},
@@ -1609,7 +1633,7 @@
"message": "Biyometri doğrulanamadı"
},
"biometricsFailedDesc": {
"message": "Biometrics cannot be completed, consider using a master password or logging out. If this persists, please contact Bitwarden support."
"message": "Biyometri doğrulaması tamamlanamadı. Ana parolanızı kullanabilir veya çıkış yapabilirsiniz. Sorun devam ederse Bitwarden destek ekibiyle iletişime geçin."
},
"nativeMessaginPermissionErrorTitle": {
"message": "İzin verilmedi"

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Автозаповнення"
},
"autoFillLogin": {
"message": "Автозаповнення входу"
},
"autoFillCard": {
"message": "Автозаповнення картки"
},
"autoFillIdentity": {
"message": "Автозаповнення особистих даних"
},
"generatePasswordCopied": {
"message": "Генерувати пароль (з копіюванням)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Немає відповідних записів"
},
"noCards": {
"message": "Немає карток"
},
"noIdentities": {
"message": "Немає особистих даних"
},
"addLoginMenu": {
"message": "Додати запис входу"
},
"addCardMenu": {
"message": "Додати картку"
},
"addIdentityMenu": {
"message": "Додати особисті дані"
},
"unlockVaultMenu": {
"message": "Розблокуйте сховище"
},
@@ -2408,18 +2432,18 @@
"description": "Toggling an expand/collapse state."
},
"aliasDomain": {
"message": "Alias domain"
"message": "Псевдонім домену"
},
"passwordRepromptDisabledAutofillOnPageLoad": {
"message": "Items with master password re-prompt cannot be auto-filled on page load. Auto-fill on page load turned off.",
"message": "Записи з повторним запитом головного пароля не можна автоматично заповнювати під час завантаження сторінки. Автозаповнення на сторінці вимкнено.",
"description": "Toast message for describing that master password re-prompt cannot be auto-filled on page load."
},
"autofillOnPageLoadSetToDefault": {
"message": "Auto-fill on page load set to use default setting.",
"message": "Автозаповнення на сторінці налаштовано з типовими параметрами.",
"description": "Toast message for informing the user that auto-fill on page load has been set to the default setting."
},
"turnOffMasterPasswordPromptToEditField": {
"message": "Turn off master password re-prompt to edit this field",
"message": "Вимкніть повторний запит головного пароля, щоб редагувати це поле",
"description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item."
}
}

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "Tự động điền"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "Tạo mật khẩu (đã sao chép)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "Không có thông tin đăng nhập phù hợp."
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "Mở khoá kho lưu trữ của bạn"
},

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "自动填充"
},
"autoFillLogin": {
"message": "自动填充登录"
},
"autoFillCard": {
"message": "自动填充支付卡"
},
"autoFillIdentity": {
"message": "自动填充身份"
},
"generatePasswordCopied": {
"message": "生成密码(并复制)"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "无匹配的登录项目"
},
"noCards": {
"message": "无支付卡"
},
"noIdentities": {
"message": "无身份"
},
"addLoginMenu": {
"message": "添加登录项目"
},
"addCardMenu": {
"message": "添加支付卡"
},
"addIdentityMenu": {
"message": "添加身份"
},
"unlockVaultMenu": {
"message": "解锁您的密码库"
},
@@ -850,7 +874,7 @@
"message": "使用此功能需要高级会员资格。"
},
"enterVerificationCodeApp": {
"message": "请输入您的验证器应用中的 6 位验证码。"
"message": "请输入您的验证器应用中的 6 位验证码。"
},
"enterVerificationCodeEmail": {
"message": "请输入发送给电子邮件 $EMAIL$ 的 6 位数验证码。",
@@ -2117,7 +2141,7 @@
}
},
"loginWithMasterPassword": {
"message": "主密码登录"
"message": "使用主密码登录"
},
"loggingInAs": {
"message": "正登录为"
@@ -2132,7 +2156,7 @@
"message": "记住电子邮件地址"
},
"loginWithDevice": {
"message": "设备登录"
"message": "使用设备登录"
},
"loginWithDeviceEnabledInfo": {
"message": "设备登录必须在 Bitwarden 应用程序的设置中启用。需要其他登录选项吗?"

View File

@@ -91,6 +91,15 @@
"autoFill": {
"message": "自動填入"
},
"autoFillLogin": {
"message": "Auto-fill login"
},
"autoFillCard": {
"message": "Auto-fill card"
},
"autoFillIdentity": {
"message": "Auto-fill identity"
},
"generatePasswordCopied": {
"message": "產生及複製密碼"
},
@@ -100,6 +109,21 @@
"noMatchingLogins": {
"message": "無符合的登入資料"
},
"noCards": {
"message": "No cards"
},
"noIdentities": {
"message": "No identities"
},
"addLoginMenu": {
"message": "Add login"
},
"addCardMenu": {
"message": "Add card"
},
"addIdentityMenu": {
"message": "Add identity"
},
"unlockVaultMenu": {
"message": "解鎖您的密碼庫"
},

View File

@@ -148,7 +148,7 @@ export default class AutofillService implements AutofillServiceInterface {
throw new Error("Nothing to auto-fill.");
}
let totpPromise: Promise<string> = null;
let totp: string | null = null;
const canAccessPremium = await this.stateService.getCanAccessPremium();
const defaultUriMatch = (await this.stateService.getDefaultUriMatch()) ?? UriMatchType.Domain;
@@ -205,15 +205,14 @@ export default class AutofillService implements AutofillServiceInterface {
if (
options.cipher.type !== CipherType.Login ||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
totpPromise ||
totp !== null ||
!options.cipher.login.totp ||
(!canAccessPremium && !options.cipher.organizationUseTotp)
) {
return;
}
totpPromise = this.stateService.getDisableAutoTotpCopy().then((disabled) => {
totp = await this.stateService.getDisableAutoTotpCopy().then((disabled) => {
if (!disabled) {
return this.totpService.getCode(options.cipher.login.totp);
}
@@ -224,8 +223,8 @@ export default class AutofillService implements AutofillServiceInterface {
if (didAutofill) {
this.eventCollectionService.collect(EventType.Cipher_ClientAutofilled, options.cipher.id);
if (totpPromise != null) {
return await totpPromise;
if (totp !== null) {
return totp;
} else {
return null;
}

View File

@@ -139,7 +139,7 @@ Bitwarden, parolları iş yoldaşlarınızla təhlükəsiz paylaşa bilməyiniz
Nəyə görə Bitwarden-i seçməliyik:
Yüksək səviyyə şifrələmə
Parollarınız qabaqcıl bir ucdan digərinə kimi şifrələmə (AES-256 bit, salted hashtag və PBKDF2 SHA-256) ilə qorunur, beləcə verilənlərinizin güvənli və gizli qalmasını təmin edir.
Parollarınız qabaqcıl ucdan-uca şifrələmə (AES-256 bit, salted hashtag və PBKDF2 SHA-256) ilə qorunur, beləcə datanızın güvənli və gizli qalmasını təmin edir.
Daxili parol yaradıcı
Çox istifadə etdiyiniz hər veb sayt üçün təhlükəsizlik tələblərinə görə güclü, unikal və təsadüfi şifrələr yaradın.

View File

@@ -19,7 +19,7 @@
"**/node_modules/@bitwarden/desktop-native/index.js",
"**/node_modules/@bitwarden/desktop-native/desktop_native.${platform}-${arch}*.node"
],
"electronVersion": "24.8.5",
"electronVersion": "26.3.0",
"generateUpdatesFilesForAllChannels": true,
"publish": {
"provider": "generic",

View File

@@ -476,7 +476,7 @@
"message": "بیشترین حجم پرونده ۵۰۰ مگابایت است."
},
"encryptionKeyMigrationRequired": {
"message": "Encryption key migration required. Please login through the web vault to update your encryption key."
"message": "انتقال کلید رمزگذاری مورد نیاز است. لطفاً از طریق گاوصندوق وب وارد شوید تا کلید رمزگذاری خود را به روز کنید."
},
"editedFolder": {
"message": "پوشه ذخیره شد"
@@ -1078,7 +1078,7 @@
"message": "۱ گیگابایت فضای ذخیره‌سازی رمزنگاری شده برای پرونده‌های پیوست."
},
"premiumSignUpTwoStepOptions": {
"message": "Proprietary two-step login options such as YubiKey and Duo."
"message": "گزینه های ورود اضافی دو مرحله ای مانند YubiKey و Duo."
},
"premiumSignUpReports": {
"message": "گزارش‌های بهداشت رمز عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما."
@@ -1493,7 +1493,7 @@
"message": "یک گاوصندوق خارج شده درخواست احراز هویت مجدد را برای دسترسی آن می‌دهد."
},
"unlockMethodNeededToChangeTimeoutActionDesc": {
"message": "Set up an unlock method to change your vault timeout action."
"message": "یک روش بازگشایی برای پایان زمان مجاز تنظیم کنید."
},
"lock": {
"message": "قفل",
@@ -1985,7 +1985,7 @@
"message": "برون ریزی گاو‌صندوق شخصی"
},
"exportingIndividualVaultDescription": {
"message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated attachments.",
"message": "فقط موارد شخصی گاوصندوق مرتبط با $EMAIL$ برون ریزی خواهند شد. موارد گاوصندوق سازمان شامل نخواهد شد. فقط اطلاعات مورد گاوصندوق برون ریزی خواهد شد و شامل تاریخچه کلمه عبور مرتبط یا پیوست نمی‌شود.",
"placeholders": {
"email": {
"content": "$1",
@@ -2110,7 +2110,7 @@
"message": "با دستگاه دیگری وارد شوید"
},
"loginInitiated": {
"message": "Login initiated"
"message": "ورود به سیستم آغاز شد"
},
"notificationSentDevice": {
"message": "یک اعلان به دستگاه شما ارسال شده است."
@@ -2250,35 +2250,35 @@
"message": "به‌روز رسانی تنظیمات توصیه شده"
},
"deviceApprovalRequired": {
"message": "Device approval required. Select an approval option below:"
"message": "تأیید دستگاه لازم است. یک روش تأیید انتخاب کنید:"
},
"rememberThisDevice": {
"message": "Remember this device"
"message": "این دستگاه را به خاطر بسپار"
},
"uncheckIfPublicDevice": {
"message": "Uncheck if using a public device"
"message": "اگر از دستگاه عمومی استفاده می‌کنید علامت را بردارید"
},
"approveFromYourOtherDevice": {
"message": "Approve from your other device"
"message": "تأیید با دستگاه دیگرتان"
},
"requestAdminApproval": {
"message": "Request admin approval"
"message": "درخواست تأیید مدیر"
},
"approveWithMasterPassword": {
"message": "Approve with master password"
"message": "تأیید با کلمه عبور اصلی"
},
"region": {
"message": "Region"
"message": "منطقه"
},
"ssoIdentifierRequired": {
"message": "Organization SSO identifier is required."
"message": "شناسه سازمان SSO مورد نیاز است."
},
"eu": {
"message": "EU",
"message": "اروپا",
"description": "European Union"
},
"loggingInOn": {
"message": "Logging in on"
"message": "ورود با"
},
"usDomain": {
"message": "bitwarden.com"
@@ -2287,46 +2287,46 @@
"message": "bitwarden.eu"
},
"selfHostedServer": {
"message": "self-hosted"
"message": "خود میزبان"
},
"accessDenied": {
"message": "دسترسی رد شد. شما اجازه مشاهده این صفحه را ندارید."
},
"accountSuccessfullyCreated": {
"message": "Account successfully created!"
"message": "حساب کاربری با موفقیت ایجاد شد!"
},
"adminApprovalRequested": {
"message": "Admin approval requested"
"message": "تأیید مدیر درخواست شد"
},
"adminApprovalRequestSentToAdmins": {
"message": "Your request has been sent to your admin."
"message": "درخواست شما به مدیرتان فرستاده شد."
},
"youWillBeNotifiedOnceApproved": {
"message": "You will be notified once approved."
"message": "به محض تأیید مطلع خواهید شد."
},
"troubleLoggingIn": {
"message": "Trouble logging in?"
"message": "در ورود مشکلی دارید؟"
},
"loginApproved": {
"message": "Login approved"
"message": "ورود تأیید شد"
},
"userEmailMissing": {
"message": "User email missing"
"message": "ایمیل کاربر وجود ندارد"
},
"deviceTrusted": {
"message": "Device trusted"
"message": "دستگاه مورد اعتماد است"
},
"inputRequired": {
"message": "Input is required."
"message": "ورودی ضروری است."
},
"required": {
"message": "required"
"message": "ضروری"
},
"search": {
"message": "Search"
"message": "جستجو"
},
"inputMinLength": {
"message": "Input must be at least $COUNT$ characters long.",
"message": "ورودی باید حداقل $COUNT$ کاراکتر داشته باشد.",
"placeholders": {
"count": {
"content": "$1",
@@ -2335,7 +2335,7 @@
}
},
"inputMaxLength": {
"message": "Input must not exceed $COUNT$ characters in length.",
"message": "طول ورودی نباید بیش از $COUNT$ کاراکتر باشد.",
"placeholders": {
"count": {
"content": "$1",
@@ -2344,7 +2344,7 @@
}
},
"inputForbiddenCharacters": {
"message": "The following characters are not allowed: $CHARACTERS$",
"message": "کاراکترهای زیر مجاز نیستند: $CHARACTERS$",
"placeholders": {
"characters": {
"content": "$1",
@@ -2353,7 +2353,7 @@
}
},
"inputMinValue": {
"message": "Input value must be at least $MIN$.",
"message": "مقدار ورودی باید حداقل $MIN$ باشد.",
"placeholders": {
"min": {
"content": "$1",
@@ -2362,7 +2362,7 @@
}
},
"inputMaxValue": {
"message": "Input value must not exceed $MAX$.",
"message": "مقدار ورودی نباید از $MAX$ تجاوز کند.",
"placeholders": {
"max": {
"content": "$1",
@@ -2371,17 +2371,17 @@
}
},
"multipleInputEmails": {
"message": "1 or more emails are invalid"
"message": "یک یا چند ایمیل نامعتبر است"
},
"inputTrimValidator": {
"message": "Input must not contain only whitespace.",
"message": "ورودی نباید فقط حاوی فضای خالی باشد.",
"description": "Notification to inform the user that a form's input can't contain only whitespace."
},
"inputEmail": {
"message": "Input is not an email address."
"message": "ورودی یک نشانی ایمیل نیست."
},
"fieldsNeedAttention": {
"message": "$COUNT$ field(s) above need your attention.",
"message": "فیلد $COUNT$ در بالا به توجه شما نیاز دارد.",
"placeholders": {
"count": {
"content": "$1",
@@ -2390,22 +2390,22 @@
}
},
"selectPlaceholder": {
"message": "-- Select --"
"message": "-- انتخاب --"
},
"multiSelectPlaceholder": {
"message": "-- Type to filter --"
"message": "-- برای فیلتر تایپ کنید --"
},
"multiSelectLoading": {
"message": "Retrieving options..."
"message": "در حال بازیابی گزینه‌ها..."
},
"multiSelectNotFound": {
"message": "No items found"
"message": "موردی یافت نشد"
},
"multiSelectClearAll": {
"message": "Clear all"
"message": "پاک‌کردن همه"
},
"plusNMore": {
"message": "+ $QUANTITY$ more",
"message": "+ $QUANTITY$ بیشتر",
"placeholders": {
"quantity": {
"content": "$1",
@@ -2414,9 +2414,9 @@
}
},
"submenu": {
"message": "Submenu"
"message": "زیرمنو"
},
"aliasDomain": {
"message": "Alias domain"
"message": "دامنه مستعار"
}
}

View File

@@ -2417,6 +2417,6 @@
"message": "Под-мени"
},
"aliasDomain": {
"message": "Alias domain"
"message": "Домен алијаса"
}
}

View File

@@ -2417,6 +2417,6 @@
"message": "Підменю"
},
"aliasDomain": {
"message": "Alias domain"
"message": "Псевдонім домену"
}
}

View File

@@ -594,7 +594,7 @@
"message": "继续"
},
"enterVerificationCodeApp": {
"message": "请输入您的身份验证器应用中的 6 位验证码。"
"message": "请输入您的验证器应用中的 6 位验证码。"
},
"enterVerificationCodeEmail": {
"message": "请输入发送给电子邮件 $EMAIL$ 的 6 位数验证码。",
@@ -2083,7 +2083,7 @@
"message": "密码库"
},
"loginWithMasterPassword": {
"message": "主密码登录"
"message": "使用主密码登录"
},
"loggingInAs": {
"message": "正登录为"

View File

@@ -2119,7 +2119,7 @@
"message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device."
},
"fingerprintPhraseHeader": {
"message": "Fingerprint phrase"
"message": "指紋短語"
},
"needAnotherOption": {
"message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?"

View File

@@ -0,0 +1,12 @@
import { NgModule } from "@angular/core";
import { CoreAuthModule } from "./core";
import { SettingsModule } from "./settings/settings.module";
@NgModule({
imports: [CoreAuthModule, SettingsModule],
declarations: [],
providers: [],
exports: [SettingsModule],
})
export class AuthModule {}

View File

@@ -0,0 +1,15 @@
import { NgModule, Optional, SkipSelf } from "@angular/core";
import { WebauthnLoginApiService } from "./services/webauthn-login/webauthn-login-api.service";
import { WebauthnLoginService } from "./services/webauthn-login/webauthn-login.service";
@NgModule({
providers: [WebauthnLoginService, WebauthnLoginApiService],
})
export class CoreAuthModule {
constructor(@Optional() @SkipSelf() parentModule?: CoreAuthModule) {
if (parentModule) {
throw new Error("CoreAuthModule is already loaded. Import it in AuthModule only");
}
}
}

View File

@@ -0,0 +1,2 @@
export * from "./services";
export * from "./core.module";

View File

@@ -0,0 +1 @@
export * from "./webauthn-login";

View File

@@ -0,0 +1 @@
export * from "./webauthn-login.service";

View File

@@ -0,0 +1,18 @@
import { WebauthnLoginAttestationResponseRequest } from "./webauthn-login-attestation-response.request";
/**
* Request sent to the server to save a newly created webauthn login credential.
*/
export class SaveCredentialRequest {
/** The response recieved from the authenticator. This contains the public key */
deviceResponse: WebauthnLoginAttestationResponseRequest;
/** Nickname chosen by the user to identify this credential */
name: string;
/**
* Token required by the server to complete the creation.
* It contains encrypted information that the server needs to verify the credential.
*/
token: string;
}

View File

@@ -0,0 +1,27 @@
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { WebauthnLoginAuthenticatorResponseRequest } from "./webauthn-login-authenticator-response.request";
/**
* The response recieved from an authentiator after a successful attestation.
* This request is used to save newly created webauthn login credentials to the server.
*/
export class WebauthnLoginAttestationResponseRequest extends WebauthnLoginAuthenticatorResponseRequest {
response: {
attestationObject: string;
clientDataJson: string;
};
constructor(credential: PublicKeyCredential) {
super(credential);
if (!(credential.response instanceof AuthenticatorAttestationResponse)) {
throw new Error("Invalid authenticator response");
}
this.response = {
attestationObject: Utils.fromBufferToB64(credential.response.attestationObject),
clientDataJson: Utils.fromBufferToB64(credential.response.clientDataJSON),
};
}
}

View File

@@ -0,0 +1,19 @@
import { Utils } from "@bitwarden/common/platform/misc/utils";
/**
* An abstract class that represents responses recieved from the webauthn authenticator.
* It contains data that is commonly returned during different types of authenticator interactions.
*/
export abstract class WebauthnLoginAuthenticatorResponseRequest {
id: string;
rawId: string;
type: string;
extensions: Record<string, unknown>;
constructor(credential: PublicKeyCredential) {
this.id = credential.id;
this.rawId = Utils.fromBufferToB64(credential.rawId);
this.type = credential.type;
this.extensions = {}; // Extensions are handled client-side
}
}

View File

@@ -0,0 +1,22 @@
import { ChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response";
import { BaseResponse } from "@bitwarden/common/models/response/base.response";
/**
* Options provided by the server to be used during attestation (i.e. creation of a new webauthn credential)
*/
export class WebauthnLoginCredentialCreateOptionsResponse extends BaseResponse {
/** Options to be provided to the webauthn authenticator */
options: ChallengeResponse;
/**
* Contains an encrypted version of the {@link options}.
* Used by the server to validate the attestation response of newly created credentials.
*/
token: string;
constructor(response: unknown) {
super(response);
this.options = new ChallengeResponse(this.getResponseProperty("options"));
this.token = this.getResponseProperty("token");
}
}

View File

@@ -0,0 +1,17 @@
import { BaseResponse } from "@bitwarden/common/models/response/base.response";
/**
* A webauthn login credential recieved from the server.
*/
export class WebauthnLoginCredentialResponse extends BaseResponse {
id: string;
name: string;
prfSupport: boolean;
constructor(response: unknown) {
super(response);
this.id = this.getResponseProperty("id");
this.name = this.getResponseProperty("name");
this.prfSupport = this.getResponseProperty("prfSupport");
}
}

View File

@@ -0,0 +1,40 @@
import { Injectable } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { Verification } from "@bitwarden/common/types/verification";
import { SaveCredentialRequest } from "./request/save-credential.request";
import { WebauthnLoginCredentialCreateOptionsResponse } from "./response/webauthn-login-credential-create-options.response";
import { WebauthnLoginCredentialResponse } from "./response/webauthn-login-credential.response";
@Injectable()
export class WebauthnLoginApiService {
constructor(
private apiService: ApiService,
private userVerificationService: UserVerificationService
) {}
async getCredentialCreateOptions(
verification: Verification
): Promise<WebauthnLoginCredentialCreateOptionsResponse> {
const request = await this.userVerificationService.buildRequest(verification);
const response = await this.apiService.send("POST", "/webauthn/options", request, true, true);
return new WebauthnLoginCredentialCreateOptionsResponse(response);
}
async saveCredential(request: SaveCredentialRequest): Promise<boolean> {
await this.apiService.send("POST", "/webauthn", request, true, true);
return true;
}
getCredentials(): Promise<ListResponse<WebauthnLoginCredentialResponse>> {
return this.apiService.send("GET", "/webauthn", null, true, true);
}
async deleteCredential(credentialId: string, verification: Verification): Promise<void> {
const request = await this.userVerificationService.buildRequest(verification);
await this.apiService.send("POST", `/webauthn/${credentialId}/delete`, request, true, true);
}
}

View File

@@ -0,0 +1,63 @@
import { mock, MockProxy } from "jest-mock-extended";
import { CredentialCreateOptionsView } from "../../views/credential-create-options.view";
import { WebauthnLoginApiService } from "./webauthn-login-api.service";
import { WebauthnLoginService } from "./webauthn-login.service";
describe("WebauthnService", () => {
let apiService!: MockProxy<WebauthnLoginApiService>;
let credentials: MockProxy<CredentialsContainer>;
let webauthnService!: WebauthnLoginService;
beforeAll(() => {
// Polyfill missing class
window.PublicKeyCredential = class {} as any;
window.AuthenticatorAttestationResponse = class {} as any;
apiService = mock<WebauthnLoginApiService>();
credentials = mock<CredentialsContainer>();
webauthnService = new WebauthnLoginService(apiService, credentials);
});
describe("createCredential", () => {
it("should return undefined when navigator.credentials throws", async () => {
credentials.create.mockRejectedValue(new Error("Mocked error"));
const options = createCredentialCreateOptions();
const result = await webauthnService.createCredential(options);
expect(result).toBeUndefined();
});
it("should return credential when navigator.credentials does not throw", async () => {
const credential = createDeviceResponse();
credentials.create.mockResolvedValue(credential as PublicKeyCredential);
const options = createCredentialCreateOptions();
const result = await webauthnService.createCredential(options);
expect(result).toBe(credential);
});
});
});
function createCredentialCreateOptions(): CredentialCreateOptionsView {
return new CredentialCreateOptionsView(Symbol() as any, Symbol() as any);
}
function createDeviceResponse(): PublicKeyCredential {
const credential = {
id: "dGVzdA==",
rawId: new Uint8Array([0x74, 0x65, 0x73, 0x74]),
type: "public-key",
response: {
attestationObject: new Uint8Array([0, 0, 0]),
clientDataJSON: "eyJ0ZXN0IjoidGVzdCJ9",
},
} as any;
Object.setPrototypeOf(credential, PublicKeyCredential.prototype);
Object.setPrototypeOf(credential.response, AuthenticatorAttestationResponse.prototype);
return credential;
}

View File

@@ -0,0 +1,109 @@
import { Injectable, Optional } from "@angular/core";
import { BehaviorSubject, filter, from, map, Observable, shareReplay, switchMap, tap } from "rxjs";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Verification } from "@bitwarden/common/types/verification";
import { CredentialCreateOptionsView } from "../../views/credential-create-options.view";
import { WebauthnCredentialView } from "../../views/webauth-credential.view";
import { SaveCredentialRequest } from "./request/save-credential.request";
import { WebauthnLoginAttestationResponseRequest } from "./request/webauthn-login-attestation-response.request";
import { WebauthnLoginApiService } from "./webauthn-login-api.service";
@Injectable()
export class WebauthnLoginService {
private navigatorCredentials: CredentialsContainer;
private _refresh$ = new BehaviorSubject<void>(undefined);
private _loading$ = new BehaviorSubject<boolean>(true);
private readonly credentials$ = this._refresh$.pipe(
tap(() => this._loading$.next(true)),
switchMap(() => this.fetchCredentials$()),
tap(() => this._loading$.next(false)),
shareReplay({ bufferSize: 1, refCount: true })
);
readonly loading$ = this._loading$.asObservable();
constructor(
private apiService: WebauthnLoginApiService,
@Optional() navigatorCredentials?: CredentialsContainer,
@Optional() private logService?: LogService
) {
// Default parameters don't work when used with Angular DI
this.navigatorCredentials = navigatorCredentials ?? navigator.credentials;
}
async getCredentialCreateOptions(
verification: Verification
): Promise<CredentialCreateOptionsView> {
const response = await this.apiService.getCredentialCreateOptions(verification);
return new CredentialCreateOptionsView(response.options, response.token);
}
async createCredential(
credentialOptions: CredentialCreateOptionsView
): Promise<PublicKeyCredential | undefined> {
const nativeOptions: CredentialCreationOptions = {
publicKey: credentialOptions.options,
};
try {
const response = await this.navigatorCredentials.create(nativeOptions);
if (!(response instanceof PublicKeyCredential)) {
return undefined;
}
return response;
} catch (error) {
this.logService?.error(error);
return undefined;
}
}
async saveCredential(
credentialOptions: CredentialCreateOptionsView,
deviceResponse: PublicKeyCredential,
name: string
) {
const request = new SaveCredentialRequest();
request.deviceResponse = new WebauthnLoginAttestationResponseRequest(deviceResponse);
request.token = credentialOptions.token;
request.name = name;
await this.apiService.saveCredential(request);
this.refresh();
}
/**
* List of webauthn credentials saved on the server.
*
* **Note:**
* - Subscribing might trigger a network request if the credentials haven't been fetched yet.
* - The observable is shared and will not create unnecessary duplicate requests.
* - The observable will automatically re-fetch if the user adds or removes a credential.
* - The observable is lazy and will only fetch credentials when subscribed to.
* - Don't subscribe to this in the constructor of a long-running service, as it will keep the observable alive.
*/
getCredentials$(): Observable<WebauthnCredentialView[]> {
return this.credentials$;
}
getCredential$(credentialId: string): Observable<WebauthnCredentialView> {
return this.credentials$.pipe(
map((credentials) => credentials.find((c) => c.id === credentialId)),
filter((c) => c !== undefined)
);
}
async deleteCredential(credentialId: string, verification: Verification): Promise<void> {
await this.apiService.deleteCredential(credentialId, verification);
this.refresh();
}
private fetchCredentials$(): Observable<WebauthnCredentialView[]> {
return from(this.apiService.getCredentials()).pipe(map((response) => response.data));
}
private refresh() {
this._refresh$.next();
}
}

View File

@@ -0,0 +1,5 @@
import { ChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response";
export class CredentialCreateOptionsView {
constructor(readonly options: ChallengeResponse, readonly token: string) {}
}

View File

@@ -0,0 +1,5 @@
export class WebauthnCredentialView {
id: string;
name: string;
prfSupport: boolean;
}

View File

@@ -0,0 +1,2 @@
export * from "./auth.module";
export * from "./core";

View File

@@ -6,7 +6,14 @@
<auth-password-callout [policy]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</auth-password-callout>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate autocomplete="off">
<form
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
ngNativeValidate
autocomplete="off"
class="tw-mb-14"
>
<div class="row">
<div class="col-6">
<div class="form-group">
@@ -118,3 +125,7 @@
{{ "changeMasterPassword" | i18n }}
</button>
</form>
<app-webauthn-login-settings
*ngIf="showWebauthnLoginSettings$ | async"
></app-webauthn-login-settings>

View File

@@ -1,6 +1,6 @@
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { firstValueFrom, Observable } from "rxjs";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -11,12 +11,13 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type";
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { UpdateKeyRequest } from "@bitwarden/common/models/request/update-key.request";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
@@ -50,6 +51,8 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
checkForBreaches = true;
characterMinimumMessage = "";
protected showWebauthnLoginSettings$: Observable<boolean>;
constructor(
i18nService: I18nService,
cryptoService: CryptoService,
@@ -65,13 +68,13 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
private apiService: ApiService,
private sendService: SendService,
private organizationService: OrganizationService,
private keyConnectorService: KeyConnectorService,
private router: Router,
private organizationApiService: OrganizationApiServiceAbstraction,
private organizationUserService: OrganizationUserService,
dialogService: DialogService,
private userVerificationService: UserVerificationService,
private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction
private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
private configService: ConfigServiceAbstraction
) {
super(
i18nService,
@@ -86,6 +89,10 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
}
async ngOnInit() {
this.showWebauthnLoginSettings$ = this.configService.getFeatureFlag$(
FeatureFlag.PasswordlessLogin
);
if (!(await this.userVerificationService.hasMasterPassword())) {
this.router.navigate(["/settings/security/two-factor"]);
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from "@angular/core";
import { PasswordCalloutComponent } from "@bitwarden/auth";
import { SharedModule } from "../../shared";
import { ChangePasswordComponent } from "./change-password.component";
import { WebauthnLoginSettingsModule } from "./webauthn-login-settings";
@NgModule({
imports: [SharedModule, WebauthnLoginSettingsModule, PasswordCalloutComponent],
declarations: [ChangePasswordComponent],
providers: [],
exports: [WebauthnLoginSettingsModule, ChangePasswordComponent],
})
export class SettingsModule {}

View File

@@ -0,0 +1,70 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-dialog dialogSize="large">
<span bitDialogTitle
>{{ "loginWithPasskey" | i18n }}
<span class="tw-text-sm tw-normal-case tw-text-muted">{{ "newPasskey" | i18n }}</span>
</span>
<ng-container bitDialogContent>
<ng-container *ngIf="currentStep === 'userVerification'">
<p bitTypography="body1">
{{ "passkeyEnterMasterPassword" | i18n }}
</p>
<bit-form-field disableMargin formGroupName="userVerification">
<bit-label>{{ "masterPassword" | i18n }}</bit-label>
<input type="password" bitInput formControlName="masterPassword" appAutofocus />
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
<bit-hint>{{ "confirmIdentity" | i18n }}</bit-hint>
</bit-form-field>
</ng-container>
<div *ngIf="currentStep === 'credentialCreation'" class="tw-flex tw-flex-col tw-items-center">
<bit-icon [icon]="Icons.CreatePasskeyIcon" class="tw-mb-6"></bit-icon>
<h3 bitTypography="h3">{{ "creatingPasskeyLoading" | i18n }}</h3>
<p bitTypography="body1">{{ "creatingPasskeyLoadingInfo" | i18n }}</p>
</div>
<div
*ngIf="currentStep === 'credentialCreationFailed'"
class="tw-flex tw-flex-col tw-items-center"
>
<bit-icon [icon]="Icons.CreatePasskeyFailedIcon" class="tw-mb-6"></bit-icon>
<h3 bitTypography="h3">{{ "errorCreatingPasskey" | i18n }}</h3>
<p bitTypography="body1">{{ "errorCreatingPasskeyInfo" | i18n }}</p>
</div>
<div *ngIf="currentStep === 'credentialNaming'">
<h3 bitTypography="h3">{{ "passkeySuccessfullyCreated" | i18n }}</h3>
<p bitTypography="body1">
{{ "customPasskeyNameInfo" | i18n }}
</p>
<bit-form-field disableMargin formGroupName="credentialNaming">
<bit-label>{{ "customName" | i18n }}</bit-label>
<input type="text" bitInput formControlName="name" appAutofocus />
<bit-hint>{{
"charactersCurrentAndMaximum"
| i18n : formGroup.value.credentialNaming.name.length : NameMaxCharacters
}}</bit-hint>
</bit-form-field>
</div>
</ng-container>
<ng-container bitDialogFooter>
<button type="submit" bitButton bitFormButton buttonType="primary">
<ng-container *ngIf="currentStep === 'userVerification'">
{{ "continue" | i18n }}
</ng-container>
<ng-container *ngIf="currentStep === 'credentialCreation'">
{{ "continue" | i18n }}
</ng-container>
<ng-container *ngIf="currentStep === 'credentialCreationFailed'">
{{ "tryAgain" | i18n }}
</ng-container>
<ng-container *ngIf="currentStep === 'credentialNaming'">
{{ ((hasPasskeys$ | async) ? "save" : "enable") | i18n }}
</ng-container>
</button>
<button type="button" bitButton bitFormButton buttonType="secondary" bitDialogClose>
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-dialog>
</form>

View File

@@ -0,0 +1,178 @@
import { DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { Component, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { firstValueFrom, map, Observable } from "rxjs";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService } from "@bitwarden/components";
import { WebauthnLoginService } from "../../../core";
import { CredentialCreateOptionsView } from "../../../core/views/credential-create-options.view";
import { CreatePasskeyFailedIcon } from "./create-passkey-failed.icon";
import { CreatePasskeyIcon } from "./create-passkey.icon";
export enum CreateCredentialDialogResult {
Success,
}
type Step =
| "userVerification"
| "credentialCreation"
| "credentialCreationFailed"
| "credentialNaming";
@Component({
templateUrl: "create-credential-dialog.component.html",
})
export class CreateCredentialDialogComponent implements OnInit {
protected readonly NameMaxCharacters = 50;
protected readonly CreateCredentialDialogResult = CreateCredentialDialogResult;
protected readonly Icons = { CreatePasskeyIcon, CreatePasskeyFailedIcon };
protected currentStep: Step = "userVerification";
protected formGroup = this.formBuilder.group({
userVerification: this.formBuilder.group({
masterPassword: ["", [Validators.required]],
}),
credentialNaming: this.formBuilder.group({
name: ["", Validators.maxLength(50)],
}),
});
protected credentialOptions?: CredentialCreateOptionsView;
protected deviceResponse?: PublicKeyCredential;
protected hasPasskeys$?: Observable<boolean>;
constructor(
private formBuilder: FormBuilder,
private dialogRef: DialogRef,
private webauthnService: WebauthnLoginService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private logService: LogService
) {}
ngOnInit(): void {
this.hasPasskeys$ = this.webauthnService
.getCredentials$()
.pipe(map((credentials) => credentials.length > 0));
}
protected submit = async () => {
this.dialogRef.disableClose = true;
try {
switch (this.currentStep) {
case "userVerification":
return await this.submitUserVerification();
case "credentialCreationFailed":
return await this.submitCredentialCreationFailed();
case "credentialCreation":
return await this.submitCredentialCreation();
case "credentialNaming":
return await this.submitCredentialNaming();
}
} finally {
this.dialogRef.disableClose = false;
}
};
protected async submitUserVerification() {
this.formGroup.controls.userVerification.markAllAsTouched();
if (this.formGroup.controls.userVerification.invalid) {
return;
}
try {
this.credentialOptions = await this.webauthnService.getCredentialCreateOptions({
type: VerificationType.MasterPassword,
secret: this.formGroup.value.userVerification.masterPassword,
});
} catch (error) {
if (error instanceof ErrorResponse && error.statusCode === 400) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("error"),
this.i18nService.t("invalidMasterPassword")
);
} else {
this.logService?.error(error);
this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError"));
}
return;
}
this.currentStep = "credentialCreation";
await this.submitCredentialCreation();
}
protected async submitCredentialCreation() {
this.deviceResponse = await this.webauthnService.createCredential(this.credentialOptions);
if (this.deviceResponse === undefined) {
this.currentStep = "credentialCreationFailed";
return;
}
this.currentStep = "credentialNaming";
}
protected async submitCredentialCreationFailed() {
this.currentStep = "credentialCreation";
await this.submitCredentialCreation();
}
protected async submitCredentialNaming() {
this.formGroup.controls.credentialNaming.markAllAsTouched();
if (this.formGroup.controls.credentialNaming.invalid) {
return;
}
const name = this.formGroup.value.credentialNaming.name;
try {
await this.webauthnService.saveCredential(
this.credentialOptions,
this.deviceResponse,
this.formGroup.value.credentialNaming.name
);
} catch (error) {
this.logService?.error(error);
this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError"));
return;
}
if (await firstValueFrom(this.hasPasskeys$)) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("passkeySaved", name)
);
} else {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("loginWithPasskeyEnabled")
);
}
this.dialogRef.close(CreateCredentialDialogResult.Success);
}
}
/**
* Strongly typed helper to open a CreateCredentialDialog
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param config Configuration for the dialog
*/
export const openCreateCredentialDialog = (
dialogService: DialogService,
config: DialogConfig<unknown>
) => {
return dialogService.open<CreateCredentialDialogResult | undefined, unknown>(
CreateCredentialDialogComponent,
config
);
};

View File

@@ -0,0 +1,28 @@
import { svgIcon } from "@bitwarden/components";
export const CreatePasskeyFailedIcon = svgIcon`
<svg xmlns="http://www.w3.org/2000/svg" width="163" height="115" fill="none">
<path class="tw-fill-secondary-500" fill-rule="evenodd" d="M31 19.46H9v22h22v-22Zm-24-2v26h26v-26H7Z"
clip-rule="evenodd" />
<path class="tw-fill-secondary-500" fill-rule="evenodd"
d="M0 43.46a4 4 0 0 1 4-4h32a4 4 0 0 1 4 4v7h-4v-7H4v16.747l1.705 2.149a4 4 0 0 1 .866 2.486v22.205a4 4 0 0 1-1 2.645L4 91.475v17.985h32V91.475l-1.572-1.783a4 4 0 0 1-1-2.645V64.842a4 4 0 0 1 .867-2.486L36 60.207V56.46h4v3.747a4 4 0 0 1-.867 2.487l-1.704 2.148v22.205L39 88.83a4 4 0 0 1 1 2.645v17.985a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V91.475a4 4 0 0 1 1-2.645l1.571-1.783V64.842L.867 62.694A4 4 0 0 1 0 60.207V43.46Z"
clip-rule="evenodd" />
<path class="tw-fill-secondary-500" fill-rule="evenodd"
d="M19.74 63.96a.5.5 0 0 1 .355.147l2.852 2.866a.5.5 0 0 1 .146.353V77.56c2.585 1.188 4.407 3.814 4.407 6.865 0 4.183-3.357 7.534-7.5 7.534-4.144 0-7.5-3.376-7.5-7.534a7.546 7.546 0 0 1 4.478-6.894v-1.443a.5.5 0 0 1 .146-.353l1.275-1.281-1.322-1.33a.5.5 0 0 1 0-.705l.332-.334-.262-.263a.5.5 0 0 1-.005-.7l1.332-1.377-1.445-1.452a.5.5 0 0 1-.145-.352v-1.114a.5.5 0 0 1 .145-.352l2.357-2.369a.5.5 0 0 1 .355-.147Zm-1.856 3.075v.7l1.645 1.654a.5.5 0 0 1 .005.7l-1.332 1.377.267.268a.5.5 0 0 1 0 .705l-.333.334 1.323 1.329a.5.5 0 0 1 0 .705l-1.48 1.488v1.57a.5.5 0 0 1-.32.466 6.545 6.545 0 0 0-4.159 6.095c0 3.61 2.913 6.534 6.5 6.534 3.588 0 6.5-2.901 6.5-6.534 0-2.749-1.707-5.105-4.095-6.074a.5.5 0 0 1-.312-.463V67.532L19.74 65.17l-1.857 1.866ZM20 85.623a1.27 1.27 0 0 0-1.268 1.276c0 .702.56 1.276 1.268 1.276.712 0 1.268-.555 1.268-1.276A1.27 1.27 0 0 0 20 85.623Zm-2.268 1.276A2.27 2.27 0 0 1 20 84.623a2.27 2.27 0 0 1 2.268 2.276c0 1.269-1 2.276-2.268 2.276a2.27 2.27 0 0 1-2.268-2.276ZM57.623 114a1 1 0 0 1 1-1h63.048a1 1 0 0 1 0 2H58.623a1 1 0 0 1-1-1Z"
clip-rule="evenodd" />
<path class="tw-fill-secondary-500" fill-rule="evenodd"
d="M78.022 114V95.654h2V114h-2ZM98.418 114V95.654h2V114h-2Z" clip-rule="evenodd" />
<path class="tw-fill-secondary-500" fill-rule="evenodd"
d="M16 14.46c0-7.732 6.268-14 14-14h119c7.732 0 14 6.268 14 14v68c0 7.732-6.268 14-14 14H39.5v-4H149c5.523 0 10-4.477 10-10v-68c0-5.523-4.477-10-10-10H30c-5.523 0-10 4.477-10 10v5h-4v-5Z"
clip-rule="evenodd" />
<path class="tw-fill-secondary-500" fill-rule="evenodd"
d="M25 15.46a6 6 0 0 1 6-6h117a6 6 0 0 1 6 6v66a6 6 0 0 1-6 6H36.5v-2H148a4 4 0 0 0 4-4v-66a4 4 0 0 0-4-4H31a4 4 0 0 0-4 4v3h-2v-3Z"
clip-rule="evenodd" />
<path class="tw-fill-secondary-500"
d="M104.269 32.86a1.42 1.42 0 0 0-1.007-.4h-25.83c-.39 0-.722.132-1.007.4a1.26 1.26 0 0 0-.425.947v16.199c0 1.207.25 2.407.75 3.597a13.22 13.22 0 0 0 1.861 3.165c.74.919 1.62 1.817 2.646 2.69a30.93 30.93 0 0 0 2.834 2.172c.868.577 1.77 1.121 2.712 1.636.942.516 1.612.862 2.007 1.043.394.181.714.326.95.42.18.083.373.128.583.128.21 0 .403-.041.582-.128.241-.099.557-.239.956-.42.394-.181 1.064-.532 2.006-1.043a36.595 36.595 0 0 0 2.712-1.636c.867-.576 1.813-1.302 2.838-2.171a19.943 19.943 0 0 0 2.646-2.69 13.24 13.24 0 0 0 1.862-3.166 9.19 9.19 0 0 0 .749-3.597V33.812c.005-.367-.14-.684-.425-.952Zm-3.329 17.298c0 5.864-10.593 10.916-10.593 10.916V35.93h10.593v14.228Z" />
<path class="tw-fill-secondary-500" fill-rule="evenodd" d="M18 24.46h-5v-2h5v2ZM27 24.46h-5v-2h5v2Z"
clip-rule="evenodd" />
<path class="tw-fill-danger-500"
d="M51.066 66.894a2.303 2.303 0 0 1-2.455-.5l-10.108-9.797L28.375 66.4l-.002.002a2.294 2.294 0 0 1-3.185.005 2.24 2.24 0 0 1-.506-2.496c.117-.27.286-.518.503-.728l10.062-9.737-9.945-9.623a2.258 2.258 0 0 1-.698-1.6c-.004-.314.06-.619.176-.894a2.254 2.254 0 0 1 1.257-1.222 2.305 2.305 0 0 1 1.723.014c.267.11.518.274.732.486l10.01 9.682 9.995-9.688.009-.008a2.292 2.292 0 0 1 3.159.026c.425.411.68.98.684 1.59a2.242 2.242 0 0 1-.655 1.6l-.01.01-9.926 9.627 10.008 9.7.029.027a2.237 2.237 0 0 1 .53 2.496l-.002.004a2.258 2.258 0 0 1-1.257 1.222Z" />
</svg>
`;

View File

@@ -0,0 +1,26 @@
import { svgIcon } from "@bitwarden/components";
export const CreatePasskeyIcon = svgIcon`
<svg xmlns="http://www.w3.org/2000/svg" width="163" height="116" fill="none">
<path class="tw-fill-secondary-500" fill-rule="evenodd" d="M31 19.58H9v22h22v-22Zm-24-2v26h26v-26H7Z"
clip-rule="evenodd" />
<path class="tw-fill-secondary-500" fill-rule="evenodd"
d="M0 43.58a4 4 0 0 1 4-4h32a4 4 0 0 1 4 4v7h-4v-7H4v16.747l1.705 2.149a4 4 0 0 1 .866 2.486v22.204a4 4 0 0 1-1 2.646L4 91.595v17.985h32V91.595l-1.572-1.783a4 4 0 0 1-1-2.646V64.962a4 4 0 0 1 .867-2.486L36 60.327V56.58h4v3.747a4 4 0 0 1-.867 2.486l-1.704 2.149v22.204L39 88.95a4 4 0 0 1 1 2.646v17.985a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V91.595a4 4 0 0 1 1-2.646l1.571-1.783V64.962L.867 62.813A4 4 0 0 1 0 60.327V43.58Z"
clip-rule="evenodd" />
<path class="tw-fill-secondary-500" fill-rule="evenodd"
d="M19.74 64.08a.5.5 0 0 1 .355.147l2.852 2.866a.5.5 0 0 1 .146.352V77.68c2.585 1.189 4.407 3.814 4.407 6.865 0 4.183-3.357 7.535-7.5 7.535-4.144 0-7.5-3.377-7.5-7.535a7.546 7.546 0 0 1 4.478-6.894V76.21a.5.5 0 0 1 .146-.353l1.275-1.282-1.322-1.329a.5.5 0 0 1 0-.705l.332-.334-.262-.263a.5.5 0 0 1-.005-.7l1.332-1.377-1.445-1.452a.5.5 0 0 1-.145-.353v-1.113a.5.5 0 0 1 .145-.353l2.357-2.368a.5.5 0 0 1 .355-.147Zm-1.856 3.074v.7l1.645 1.654a.5.5 0 0 1 .005.7l-1.332 1.377.267.268a.5.5 0 0 1 0 .706l-.333.334 1.323 1.329a.5.5 0 0 1 0 .705l-1.48 1.488v1.57a.5.5 0 0 1-.32.466 6.545 6.545 0 0 0-4.159 6.094c0 3.61 2.913 6.535 6.5 6.535 3.588 0 6.5-2.902 6.5-6.535 0-2.748-1.707-5.104-4.095-6.073a.5.5 0 0 1-.312-.463V67.651l-2.352-2.364-1.857 1.866ZM20 85.742a1.27 1.27 0 0 0-1.268 1.277c0 .701.56 1.276 1.268 1.276.712 0 1.268-.555 1.268-1.276A1.27 1.27 0 0 0 20 85.742Zm-2.268 1.277A2.27 2.27 0 0 1 20 84.742a2.27 2.27 0 0 1 2.268 2.277c0 1.268-1 2.276-2.268 2.276a2.27 2.27 0 0 1-2.268-2.276ZM41.796 42.844a1 1 0 0 1 1.413.058l5.526 6A1 1 0 0 1 48 50.58H27a1 1 0 1 1 0-2h18.72l-3.982-4.323a1 1 0 0 1 .058-1.413ZM33.315 62.315a1 1 0 0 1-1.413-.058l-5.526-6a1 1 0 0 1 .735-1.677h21a1 1 0 1 1 0 2h-18.72l3.982 4.322a1 1 0 0 1-.058 1.413ZM57.623 114.12a1 1 0 0 1 1-1h63.048a1 1 0 1 1 0 2H58.623a1 1 0 0 1-1-1Z"
clip-rule="evenodd" />
<path class="tw-fill-secondary-500" fill-rule="evenodd"
d="M78.022 114.12V95.774h2v18.346h-2ZM98.418 114.12V95.774h2v18.346h-2Z" clip-rule="evenodd" />
<path class="tw-fill-secondary-500" fill-rule="evenodd"
d="M16 14.58c0-7.732 6.268-14 14-14h119c7.732 0 14 6.268 14 14v68c0 7.732-6.268 14-14 14H39.5v-4H149c5.523 0 10-4.478 10-10v-68c0-5.523-4.477-10-10-10H30c-5.523 0-10 4.477-10 10v5h-4v-5Z"
clip-rule="evenodd" />
<path class="tw-fill-secondary-500" fill-rule="evenodd"
d="M25 15.58a6 6 0 0 1 6-6h117a6 6 0 0 1 6 6v66a6 6 0 0 1-6 6H36.5v-2H148a4 4 0 0 0 4-4v-66a4 4 0 0 0-4-4H31a4 4 0 0 0-4 4v3h-2v-3Z"
clip-rule="evenodd" />
<path class="tw-fill-secondary-500"
d="M104.269 32.98a1.42 1.42 0 0 0-1.007-.4h-25.83c-.39 0-.722.132-1.007.4a1.26 1.26 0 0 0-.425.947v16.199c0 1.207.25 2.406.75 3.597a13.222 13.222 0 0 0 1.861 3.165c.74.919 1.62 1.817 2.646 2.69a30.93 30.93 0 0 0 2.834 2.172c.868.577 1.77 1.121 2.712 1.636.942.515 1.612.861 2.007 1.043.394.18.714.325.95.42.18.082.373.128.583.128.21 0 .403-.042.582-.128.241-.099.557-.24.956-.42.394-.182 1.064-.532 2.006-1.043a36.56 36.56 0 0 0 2.712-1.636c.867-.577 1.813-1.302 2.838-2.172a19.943 19.943 0 0 0 2.646-2.69 13.24 13.24 0 0 0 1.862-3.165c.5-1.187.749-2.386.749-3.597V33.93c.005-.367-.14-.684-.425-.952Zm-3.329 17.298c0 5.864-10.593 10.916-10.593 10.916V36.049h10.593v14.23Z" />
<path class="tw-fill-secondary-500" fill-rule="evenodd" d="M18 24.58h-5v-2h5v2ZM27 24.58h-5v-2h5v2Z"
clip-rule="evenodd" />
</svg>
`;

View File

@@ -0,0 +1,34 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-dialog dialogSize="large">
<span bitDialogTitle
>{{ "removePasskey" | i18n }}
<span *ngIf="credential" class="tw-text-sm tw-normal-case tw-text-muted">{{
credential.name
}}</span>
</span>
<ng-container bitDialogContent>
<ng-container *ngIf="!credential">
<i class="bwi bwi-spinner bwi-spin tw-ml-1" aria-hidden="true"></i>
</ng-container>
<ng-container *ngIf="credential">
<p bitTypography="body1">{{ "removePasskeyInfo" | i18n }}</p>
<bit-form-field disableMargin>
<bit-label>{{ "masterPassword" | i18n }}</bit-label>
<input type="password" bitInput formControlName="masterPassword" />
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
<bit-hint>{{ "confirmIdentity" | i18n }}</bit-hint>
</bit-form-field>
</ng-container>
</ng-container>
<ng-container bitDialogFooter>
<button type="submit" bitButton bitFormButton buttonType="danger">
{{ "remove" | i18n }}
</button>
<button type="button" bitButton bitFormButton buttonType="secondary" bitDialogClose>
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-dialog>
</form>

View File

@@ -0,0 +1,95 @@
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Subject, takeUntil } from "rxjs";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService } from "@bitwarden/components";
import { WebauthnLoginService } from "../../../core";
import { WebauthnCredentialView } from "../../../core/views/webauth-credential.view";
export interface DeleteCredentialDialogParams {
credentialId: string;
}
@Component({
templateUrl: "delete-credential-dialog.component.html",
})
export class DeleteCredentialDialogComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
protected formGroup = this.formBuilder.group({
masterPassword: ["", [Validators.required]],
});
protected credential?: WebauthnCredentialView;
constructor(
@Inject(DIALOG_DATA) private params: DeleteCredentialDialogParams,
private formBuilder: FormBuilder,
private dialogRef: DialogRef,
private webauthnService: WebauthnLoginService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private logService: LogService
) {}
ngOnInit(): void {
this.webauthnService
.getCredential$(this.params.credentialId)
.pipe(takeUntil(this.destroy$))
.subscribe((credential) => (this.credential = credential));
}
submit = async () => {
if (this.credential === undefined) {
return;
}
this.dialogRef.disableClose = true;
try {
await this.webauthnService.deleteCredential(this.credential.id, {
type: VerificationType.MasterPassword,
secret: this.formGroup.value.masterPassword,
});
this.platformUtilsService.showToast("success", null, this.i18nService.t("passkeyRemoved"));
} catch (error) {
if (error instanceof ErrorResponse && error.statusCode === 400) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("error"),
this.i18nService.t("invalidMasterPassword")
);
} else {
this.logService.error(error);
this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError"));
}
return false;
} finally {
this.dialogRef.disableClose = false;
}
this.dialogRef.close();
};
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}
/**
* Strongly typed helper to open a DeleteCredentialDialogComponent
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param config Configuration for the dialog
*/
export const openDeleteCredentialDialogComponent = (
dialogService: DialogService,
config: DialogConfig<DeleteCredentialDialogParams>
) => {
return dialogService.open<unknown>(DeleteCredentialDialogComponent, config);
};

View File

@@ -0,0 +1 @@
export * from "./webauthn-login-settings.module";

View File

@@ -0,0 +1,71 @@
<h2 bitTypography="h2">
{{ "loginWithPasskey" | i18n }}
<ng-container *ngIf="hasData">
<span *ngIf="hasCredentials" bitBadge badgeType="success" class="!tw-align-middle">{{
"on" | i18n
}}</span>
<span *ngIf="!hasCredentials" bitBadge badgeType="secondary" class="!tw-align-middle">{{
"off" | i18n
}}</span>
</ng-container>
<ng-container *ngIf="loading">
<i class="bwi bwi-spinner bwi-spin tw-ml-1" aria-hidden="true"></i>
</ng-container>
</h2>
<p bitTypography="body1">
{{ "loginWithPasskeyInfo" | i18n }}
<a bitLink href="https://bitwarden.com/help/login-with-passkeys">{{
"learnMoreAboutPasswordless" | i18n
}}</a>
</p>
<table *ngIf="hasCredentials" class="tw-mb-5">
<tr *ngFor="let credential of credentials">
<td class="tw-p-2 tw-pl-0 tw-font-semibold">{{ credential.name }}</td>
<td class="tw-p-2 tw-pr-0">
<ng-container *ngIf="credential.prfSupport">
<i class="bwi bwi-lock-encrypted"></i>
{{ "supportsEncryption" | i18n }}
</ng-container>
<span bitTypography="body1" class="tw-text-muted">
{{ "encryptionNotSupported" | i18n }}
</span>
</td>
<td class="tw-py-2 tw-pl-10 tw-pr-0">
<button
type="button"
bitLink
[disabled]="loading"
[attr.aria-label]="('remove' | i18n) + ' ' + credential.name"
(click)="deleteCredential(credential.id)"
>
{{ "remove" | i18n }}
</button>
</td>
</tr>
</table>
<p bitTypography="body2" *ngIf="limitReached">{{ "passkeyLimitReachedInfo" | i18n }}</p>
<ng-container *ngIf="hasData && !limitReached">
<button
*ngIf="hasCredentials"
type="button"
bitButton
[disabled]="loading"
(click)="createCredential()"
>
{{ "newPasskey" | i18n }}
</button>
<button
*ngIf="!hasCredentials"
type="button"
bitButton
[attr.aria-label]="('enable' | i18n) + ' ' + ('loginWithPasskey' | i18n)"
[disabled]="loading"
(click)="createCredential()"
>
{{ "enable" | i18n }}
</button>
</ng-container>

View File

@@ -0,0 +1,72 @@
import { Component, HostBinding, OnDestroy, OnInit } from "@angular/core";
import { Subject, takeUntil } from "rxjs";
import { DialogService } from "@bitwarden/components";
import { WebauthnLoginService } from "../../core";
import { WebauthnCredentialView } from "../../core/views/webauth-credential.view";
import { openCreateCredentialDialog } from "./create-credential-dialog/create-credential-dialog.component";
import { openDeleteCredentialDialogComponent } from "./delete-credential-dialog/delete-credential-dialog.component";
@Component({
selector: "app-webauthn-login-settings",
templateUrl: "webauthn-login-settings.component.html",
host: {
"aria-live": "polite",
},
})
export class WebauthnLoginSettingsComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
protected readonly MaxCredentialCount = 5;
protected credentials?: WebauthnCredentialView[];
protected loading = true;
constructor(
private webauthnService: WebauthnLoginService,
private dialogService: DialogService
) {}
@HostBinding("attr.aria-busy")
get ariaBusy() {
return this.loading ? "true" : "false";
}
get hasCredentials() {
return this.credentials && this.credentials.length > 0;
}
get hasData() {
return this.credentials !== undefined;
}
get limitReached() {
return this.credentials?.length >= this.MaxCredentialCount;
}
ngOnInit(): void {
this.webauthnService
.getCredentials$()
.pipe(takeUntil(this.destroy$))
.subscribe((credentials) => (this.credentials = credentials));
this.webauthnService.loading$
.pipe(takeUntil(this.destroy$))
.subscribe((loading) => (this.loading = loading));
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
protected createCredential() {
openCreateCredentialDialog(this.dialogService, {});
}
protected deleteCredential(credentialId: string) {
openDeleteCredentialDialogComponent(this.dialogService, { data: { credentialId } });
}
}

View File

@@ -0,0 +1,19 @@
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { SharedModule } from "../../../shared/shared.module";
import { CreateCredentialDialogComponent } from "./create-credential-dialog/create-credential-dialog.component";
import { DeleteCredentialDialogComponent } from "./delete-credential-dialog/delete-credential-dialog.component";
import { WebauthnLoginSettingsComponent } from "./webauthn-login-settings.component";
@NgModule({
imports: [SharedModule, FormsModule, ReactiveFormsModule],
declarations: [
WebauthnLoginSettingsComponent,
CreateCredentialDialogComponent,
DeleteCredentialDialogComponent,
],
exports: [WebauthnLoginSettingsComponent],
})
export class WebauthnLoginSettingsModule {}

View File

@@ -6,9 +6,10 @@ import { FormFieldModule } from "@bitwarden/components";
import { OrganizationCreateModule } from "../../admin-console/organizations/create/organization-create.module";
import { RegisterFormModule } from "../../auth/register-form/register-form.module";
import { PaymentComponent, TaxInfoComponent } from "../../billing";
import { BillingComponent } from "../../billing/accounts/trial-initiation/billing.component";
import { EnvironmentSelectorModule } from "../../components/environment-selector/environment-selector.module";
import { LooseComponentsModule, SharedModule } from "../../shared";
import { SharedModule } from "../../shared";
import { ConfirmationDetailsComponent } from "./confirmation-details.component";
import { AbmEnterpriseContentComponent } from "./content/abm-enterprise-content.component";
@@ -37,8 +38,9 @@ import { VerticalStepperModule } from "./vertical-stepper/vertical-stepper.modul
FormFieldModule,
RegisterFormModule,
OrganizationCreateModule,
LooseComponentsModule,
EnvironmentSelectorModule,
PaymentComponent,
TaxInfoComponent,
],
declarations: [
TrialInitiationComponent,

View File

@@ -40,7 +40,7 @@
<bit-form-control>
<input type="checkbox" bitCheckbox formControlName="enabled" />
<bit-label>{{ "addSecretsManager" | i18n }}</bit-label>
<bit-label>{{ "subscribeToSecretsManager" | i18n }}</bit-label>
<bit-hint *ngIf="upgradeOrganization">{{ "addSecretsManagerUpgradeDesc" | i18n }}</bit-hint>
</bit-form-control>

View File

@@ -1,6 +1,7 @@
import { NgModule } from "@angular/core";
import { OrganizationUserModule } from "./admin-console/organizations/users/organization-user.module";
import { AuthModule } from "./auth";
import { LoginModule } from "./auth/login/login.module";
import { TrialInitiationModule } from "./auth/trial-initiation/trial-initiation.module";
import { LooseComponentsModule, SharedModule } from "./shared";
@@ -16,6 +17,7 @@ import { VaultFilterModule } from "./vault/individual-vault/vault-filter/vault-f
OrganizationBadgeModule,
OrganizationUserModule,
LoginModule,
AuthModule,
],
exports: [
SharedModule,

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