diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6b6a905bfdd..78003b00ff2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -18,6 +18,9 @@ apps/web/src/connectors @bitwarden/team-auth-dev bitwarden_license/bit-web/src/app/auth @bitwarden/team-auth-dev libs/angular/src/auth @bitwarden/team-auth-dev libs/common/src/auth @bitwarden/team-auth-dev +# biometrics +apps/desktop/src/services/native-messaging.service.ts @bitwarden/team-auth-dev +app/browser/src/background/nativeMessaging.background.ts @bitwarden/team-auth-dev ## Tools team files ## apps/browser/src/tools @bitwarden/team-tools-dev @@ -92,7 +95,6 @@ libs/common/src/autofill @bitwarden/team-autofill-dev # DuckDuckGo integration apps/desktop/native-messaging-test-runner @bitwarden/team-autofill-dev apps/desktop/src/services/native-message-handler.service.ts @bitwarden/team-autofill-dev -apps/desktop/src/services/native-messaging.service.ts @bitwarden/team-autofill-dev ## Component Library ## .storybook @bitwarden/team-design-system diff --git a/.storybook/main.ts b/.storybook/main.ts index 175ed339489..0dd7094fac0 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -6,6 +6,8 @@ const config: StorybookConfig = { stories: [ "../libs/auth/src/**/*.mdx", "../libs/auth/src/**/*.stories.@(js|jsx|ts|tsx)", + "../libs/tools/send/send-ui/src/**/*.mdx", + "../libs/tools/send/send-ui/src/**/*.stories.@(js|jsx|ts|tsx)", "../libs/vault/src/**/*.mdx", "../libs/vault/src/**/*.stories.@(js|jsx|ts|tsx)", "../libs/components/src/**/*.mdx", diff --git a/apps/browser/package.json b/apps/browser/package.json index 3c8eb50f387..bbcf0badbc5 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -6,7 +6,7 @@ "build:mv2": "webpack", "build:watch": "cross-env MANIFEST_VERSION=3 webpack --watch", "build:watch:mv2": "webpack --watch", - "build:prod": "cross-env NODE_ENV=production webpack", + "build:prod": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=4096\" webpack", "build:prod:beta": "cross-env BETA_BUILD=1 NODE_ENV=production webpack", "build:prod:watch": "cross-env NODE_ENV=production webpack --watch", "dist": "npm run build:prod && gulp dist", diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 4eb6c139792..40bd4c91ab5 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "رمز التحقق مطلوب." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "رمز التحقق غير صالح" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "مسح رمز QR للمصادقة من صفحة الويب الحالية" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "نسخ مفتاح المصادقة (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "انتهت صلاحية جلسة تسجيل الدخول الخاصة بك." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "هل أنت متأكد من أنك تريد تسجيل الخروج؟" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "كلمة المرور الرئيسية الجديدة لا تفي بمتطلبات السياسة العامة." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "قم بتشغيل Duo واتبع الخطوات لإنهاء تسجيل الدخول." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 72490926acf..068a94864fd 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Doğrulama kodu tələb olunur." }, + "webauthnCancelOrTimeout": { + "message": "Kimlik doğrulama ləğv edildi və ya çox uzun çəkdi. Lütfən yenidən sınayın." + }, "invalidVerificationCode": { "message": "Yararsız doğrulama kodu" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Hazırkı veb sahifədən kimlik doğrulayıcı QR kodunu skan et" }, + "totpHelperTitle": { + "message": "2 addımlı doğrulama problemsiz edin" + }, + "totpHelper": { + "message": "Bitwarden, 2 addımlı doğrulama kodlarını saxlaya və doldura bilər. Açarı kopyalayıb bu xanaya yapışdırın." + }, + "totpHelperWithCapture": { + "message": "Bitwarden, 2 addımlı doğrulama kodlarını saxlaya və doldura bilər. Bu veb sayt kimlik doğrulayıcı QR kodunun ekran şəklini çəkmək üçün kamera ikonunu seçin, ya da açarı kopyalayıb bu xanaya yapışdırın." + }, "copyTOTP": { "message": "Kimlik doğrulayıcı açarını kopyala (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Giriş seansınızın müddəti bitdi." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Çıxış etmək istədiyinizə əminsiniz?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Yeni ana parolunuz siyasət tələblərini qarşılamır." }, - "receiveMarketingEmails": { - "message": "Elanlar, məsləhətlər və araşdırma fürsətləri üçün Bitwarden-dən e-poçt alın." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Abunəlikdən çıx" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Cihaz güvənlidir" }, + "sendsNoItemsTitle": { + "message": "Aktiv \"Send\" yoxdur", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Şifrələnmiş məlumatları hər kəslə güvənli şəkildə paylaşmaq üçün \"Send\"i istifadə edin.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Giriş lazımdır." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Duo xidmətinə bağlanarkən xəta baş verdi. Fərqli iki addımlı giriş üsulu istifadə edin və ya kömək üçün Duo ilə əlaqə saxlayın." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Duo-nu başladın və giriş prosesini tamamlamaq üçün addımları izləyin." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Giriş məlumatları" + }, + "authenticatorKey": { + "message": "Kimlik doğrulayıcı açarı" + }, "cardDetails": { "message": "Kart detalları" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Hesab əlavə et" + }, + "loading": { + "message": "Yüklənir" + }, + "assign": { + "message": "Təyin et" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Yalnız bu kolleksiyalara müraciəti olan təşkilat üzvləri bu elementləri görə biləcək." + }, + "bulkCollectionAssignmentWarning": { + "message": "$TOTAL_COUNT$ element seçmisiniz. Düzəliş icazəniz olmadığı üçün $READONLY_COUNT$ elementi güncəlləyə bilməzsiniz.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Xana əlavə et" + }, + "add": { + "message": "Əlavə et" + }, + "fieldType": { + "message": "Xana növü" + }, + "fieldLabel": { + "message": "Xana etiketi" + }, + "textHelpText": { + "message": "Təhlükəsizlik sualları kimi datalar üçün mətn xanalarını istifadə edin" + }, + "hiddenHelpText": { + "message": "Parol kimi həssas datalar üçün gizli xanaları istifadə edin" + }, + "checkBoxHelpText": { + "message": "\"E-poçtu xatırla\" kimi formun təsdiq qutusunu avto-doldurmaq istəyirsinizsə təsdiq qutularını istifadə edin" + }, + "linkedHelpText": { + "message": "Müəyyən bir veb sayt üçün avto-doldurma problemləri ilə üzləşdikdə əlaqələndirilmiş xana istifadə edin." + }, + "linkedLabelHelpText": { + "message": "Xana üçün bunları daxil edin: kimlik, ad, aria-label və ya placeholder." + }, + "editField": { + "message": "Xanaya düzəliş et" + }, + "editFieldLabel": { + "message": "$LABEL$ - düzəliş et", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "$LABEL$ - sil", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ əlavə edildi", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "$LABEL$ - yenidən sırala. Ox düyməsi ilə elementi yuxarı və ya aşağı daşıyın.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ yuxarı daşındı, mövqeyi $INDEX$/$LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Təyin ediləcək kolleksiyaları seçin" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ seçilmiş təşkilata birdəfəlik transfer ediləcək. Artıq bu elementlərə sahib olmaya bilməyəcəksiniz.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$, $ORG$ təşkilatına birdəfəlik transfer ediləcək. Artıq bu elementlərə sahib olmaya bilməyəcəksiniz.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Uğurla təyin edilən kolleksiyalar" + }, + "nothingSelected": { + "message": "Heç nə seçməmisiniz." + }, + "movedItemsToOrg": { + "message": "Seçilən elementlər $ORGNAME$ təşkilatına daşınıldı", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ aşağı daşındı, mövqeyi $INDEX$/$LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index b4fdffc479d..10187da7dd5 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Патрабуецца праверачны код." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Памылковы праверачны код" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Тэрмін дзеяння вашага сеансу завяршыўся." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Вы ўпэўнены, што хочаце выйсці?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Ваш новы асноўны пароль не адпавядае патрабаванням палітыкі." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index db1c266177b..2379fdb8186 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Кодът за потвърждение е задължителен." }, + "webauthnCancelOrTimeout": { + "message": "Удостоверяването беше отменено или отне твърде много време. Моля, опитайте отново." + }, "invalidVerificationCode": { "message": "Грешен код за потвърждаване" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Сканирайте QR-кода за удостоверяване от текущата страница" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Копиране на удостоверителния ключ (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Сесията ви изтече." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Сигурни ли сте, че искате да се отпишете?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Паролата ви не отговаря на политиките." }, - "receiveMarketingEmails": { - "message": "Получавайте е-писма от Битоурден за новини, съвети и възможности за проучвания." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Отписване" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Устройството е доверено" }, + "sendsNoItemsTitle": { + "message": "Няма активни Изпращания", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Използвайте Изпращане, за да споделите безопасно шифрована информация с някого.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Полето е задължително да бъде попълнено." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Грешка при свързването с услугата на Duo. Използвайте друг метод за двустепенно удостоверяване или се свържете с Duo за съдействие." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Стартирайте DUO и следвайте инструкциите, за да завършите вписването." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Данни за картата" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Добавяне на регистрация" + }, + "loading": { + "message": "Зареждане" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Добавяне на поле" + }, + "add": { + "message": "Добавяне" + }, + "fieldType": { + "message": "Тип на полето" + }, + "fieldLabel": { + "message": "Етикет на полето" + }, + "textHelpText": { + "message": "Използвайте текстови полета за обикновени данни, например въпроси за сигурност" + }, + "hiddenHelpText": { + "message": "Използвайте скрити полета за чувствителни данни, например пароли" + }, + "checkBoxHelpText": { + "message": "Използвайте полета за отметки, ако искате да попълвате автоматично такива полета във формуляри, например такова за запомняне на е-пощата" + }, + "linkedHelpText": { + "message": "Използвайте свързано поле, когато имате проблеми с автоматичното попълване на конкретен уеб сайт." + }, + "linkedLabelHelpText": { + "message": "Въведете подробности за полето от атрибутите му в HTML – id, name, aria-label или placeholder." + }, + "editField": { + "message": "Редактиране на полето" + }, + "editFieldLabel": { + "message": "Редактиране на $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Изтриване на $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "Добавено: $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Преместване на $LABEL$. Използвайте стрелките, за да преместите елемента нагоре или надолу.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "Преместено нагоре: $LABEL$. Позиция $INDEX$ от $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "Преместено надолу: $LABEL$. Позиция $INDEX$ от $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 528248476d0..ad80eaf1b04 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "যাচাইকরণ কোড প্রয়োজন।" }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "আপনার লগইন মাত্রকালটির মেয়াদ শেষ হয়ে গেছে।" }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "আপনি লগ আউট করতে চান?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "আপনার নতুন মূল পাসওয়ার্ড নীতির প্রয়োজনীয়তা পূরণ করে না।" }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 0040eb2a433..b80f37fabce 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Your login session has expired." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 467673e6c91..ba16faf012c 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "El codi de verificació és obligatori." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Codi de verificació no vàlid" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Escaneja el codi QR de l'autenticador des de la pàgina web actual" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copia la clau de l'autenticador (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "La vostra sessió ha caducat." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Segur que voleu tancar la sessió?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "La nova contrasenya principal no compleix els requisits de la política." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Dispositiu de confiança" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "L'entrada és obligatòria." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Inicieu DUO i seguiu els passos per finalitzar la sessió." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 1067219e023..17e7869fd3a 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Je vyžadován ověřovací kód." }, + "webauthnCancelOrTimeout": { + "message": "Ověření bylo zrušeno nebo trvalo příliš dlouho. Zkuste to znovu." + }, "invalidVerificationCode": { "message": "Neplatný ověřovací kód" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Naskenovat QR kód z aktuální webové stránky" }, + "totpHelperTitle": { + "message": "Bezproblémové dvoufázové ověřování" + }, + "totpHelper": { + "message": "Bitwarden umí ukládat a vyplňovat dvoufázové ověřovací kódy. Zkopírujte a vložte klíč do tohoto pole." + }, + "totpHelperWithCapture": { + "message": "Bitwarden umí ukládat a vyplňovat dvoufázové ověřovací kódy. Vyberte ikonu fotoaparátu a pořiďte snímek obrazovky ověřovacího QR kódu této webové stránky nebo klíč zkopírujte a vložte do tohoto pole." + }, "copyTOTP": { "message": "Kopírovat autentizační klíč (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Platnost přihlášení vypršela." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Opravdu se chcete odhlásit?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Vaše nové hlavní heslo nesplňuje požadavky zásad organizace." }, - "receiveMarketingEmails": { - "message": "Získejte e-maily od Bitwardenu pro oznámení, poradenství a výzkumné příležitosti." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Odhlásit odběr" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Zařízení zařazeno mezi důvěryhodné" }, + "sendsNoItemsTitle": { + "message": "Žádná aktivní Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Použijte Send pro bezpečné sdílení šifrovaných informací s kýmkoliv.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Je vyžadován vstup." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Chyba při připojování ke službě Duo. Použijte jinou dvoufázovou metodu přihlášení nebo kontaktujte Duo o pomoc." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Spusťte DUO a pro dokončení přihlášení postupujte podle kroků." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Přihlašovací údaje" + }, + "authenticatorKey": { + "message": "Ověřovací klíč" + }, "cardDetails": { "message": "Card details" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Přidat účet" + }, + "loading": { + "message": "Načítání" + }, + "assign": { + "message": "Přiřadit" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Jen členové organizace s přístupem k těmto kolekcím budou moci vidět položky." + }, + "bulkCollectionAssignmentWarning": { + "message": "Vybrali jste $TOTAL_COUNT$ položek. Nemůžete aktualizovat $READONLY_COUNT$ položek, protože nemáte oprávnění k úpravám.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Přidat pole" + }, + "add": { + "message": "Přidat" + }, + "fieldType": { + "message": "Typ pole" + }, + "fieldLabel": { + "message": "Popis pole" + }, + "textHelpText": { + "message": "Použijte textová pole pro data jako bezpečnostní otázky" + }, + "hiddenHelpText": { + "message": "Použijte skrytá pole pro citlivá data, jako je heslo" + }, + "checkBoxHelpText": { + "message": "Použijte zaškrtávací políčka, pokud chcete automaticky vyplnit zaškrtávací políčko formuláře (např. pro zapamatování e-mailu)" + }, + "linkedHelpText": { + "message": "Použijte propojené pole, pokud máte problémy s automatickým vyplňováním na konkrétní webové stránce" + }, + "linkedLabelHelpText": { + "message": "Zadejte ID pole z HTML, název, popisek nebo zástupný znak pole." + }, + "editField": { + "message": "Upravit pole" + }, + "editFieldLabel": { + "message": "Upravit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Smazat $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ - přidáno", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Změnit pořadí $LABEL$. Použijte šipky pro posunutí položky nahoru nebo dolů.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ - přesunuto nahoru, pozice $INDEX$ z $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Vyberte kolekce pro přiřazení" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ bude trvale převedeno do vybrané organizace. Tyto položky již nebudete vlastnit.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ bude trvale převedeno do $ORG$. Tyto položky již nebudete vlastnit.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Kolekce byly úspěšně přiřazeny" + }, + "nothingSelected": { + "message": "Nevybrali jste žádné položky." + }, + "movedItemsToOrg": { + "message": "Vybrané položky přesunuty do $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ - přesunuto dolů, pozice $INDEX$ z $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 55c7b494e16..39ddf6ff5cc 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Mae angen cod dilysu." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Cod dilysu annilys" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Mae eich sesiwn wedi dod i ben." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Ydych chi'n siŵr eich bod am allgofnodi?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 5ff1553f895..6433d570d5a 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Bekræftelseskode er påkrævet." }, + "webauthnCancelOrTimeout": { + "message": "Godkendelsen blev afbrudt eller tog for lang tid. Forsøg igen." + }, "invalidVerificationCode": { "message": "Ugyldig bekræftelseskode" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Skan godkendelses QR-kode fra den aktuelle webside" }, + "totpHelperTitle": { + "message": "Gør 2-trinsbekræftelse problemfri" + }, + "totpHelper": { + "message": "Bitwarden kan gemme og udfylde 2-trinsbekræftelseskoder. Kopiér og indsæt nøglen i dette felt." + }, + "totpHelperWithCapture": { + "message": "Bitwarden kan gemme og udfylde 2-trinsbekræftelseskoder. Vælg kameraikonet for at tage et skærmfoto af dette websteds godkendelses-QR-kode, eller kopiér og indsæt nøglen i dette felt." + }, "copyTOTP": { "message": "Kopiér godkendelsesnøgle (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Din login-session er udløbet." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Er du sikker på, du vil logge ud?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Din nye hovedadgangskode opfylder ikke politikkravene." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Afmeld" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Enhed betroet" }, + "sendsNoItemsTitle": { + "message": "Ingen aktive Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Brug Send til at dele krypterede oplysninger sikkert med nogen.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input obligatorisk." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Fejl under forbindelsesoprettelsen til Duo-tjenesten. Brug en anden totrins-indlogningsmetode eller kontakt Duo for hjælp." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Start Duo og følg trinnene for at fuldføre indlogningen." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login-legitimationsoplysninger" + }, + "authenticatorKey": { + "message": "Godkendelsesnøgle" + }, "cardDetails": { "message": "Kortoplysninger" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Tilføj konto" + }, + "loading": { + "message": "Indlæser" + }, + "assign": { + "message": "Tildel" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Kun organisationsmedlemmer med adgang til disse samlinger vil kunne se emnerne." + }, + "bulkCollectionAssignmentWarning": { + "message": "Der er valgt $TOTAL_COUNT$ emner. $READONLY_COUNT$ af emnerne kan ikke opdateres grundet manglende redigeringsrettigheder.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Tilføj felt" + }, + "add": { + "message": "Tilføj" + }, + "fieldType": { + "message": "Felttype" + }, + "fieldLabel": { + "message": "Feltetiket" + }, + "textHelpText": { + "message": "Brug tekstfelter til data, såsom sikkerhedsspørgsmål" + }, + "hiddenHelpText": { + "message": "Brug skjulte felter til sensitive data, såsom en adgangskode" + }, + "checkBoxHelpText": { + "message": "Brug afkrydsningsfelter, hvis et formularafkrydsningsfelt skal autoudfyldes, såsom en husket e-mail" + }, + "linkedHelpText": { + "message": "Brug et linket felt ved autoudfyldningsproblemer for et bestemt websted." + }, + "linkedLabelHelpText": { + "message": "Angiv feltets HTML-ID, navn, aria-label eller variabel." + }, + "editField": { + "message": "Redigér felt" + }, + "editFieldLabel": { + "message": "Redigér $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Slet $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ tilføjet", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Omarrangér $LABEL$. Brug piletasterne til at flytte elementet op eller ned.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ flyttet op, position $INDEX$ af $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Vælg samlinger at tildele" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ overføres permanent til den valgte organisation. Man vil ikke længere eje disse emner.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ overføres permanent til $ORG$. Man vil ikke længere eje disse emner.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Samlinger hermed tildelt" + }, + "nothingSelected": { + "message": "Ingenting er valgt." + }, + "movedItemsToOrg": { + "message": "Valgte emner flyttet til $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ flyttet ned, position $INDEX$ af $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 444b2df0977..689846fbf60 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Verifizierungscode wird benötigt." }, + "webauthnCancelOrTimeout": { + "message": "Die Authentifizierung wurde abgebrochen oder hat zu lange gedauert. Bitte versuche es erneut." + }, "invalidVerificationCode": { "message": "Ungültiger Verifizierungscode" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Den Authentifizierungs-QR-Code von der aktuellen Webseite scannen" }, + "totpHelperTitle": { + "message": "Zwei-Faktor-Authentifizierung nahtlos gestalten" + }, + "totpHelper": { + "message": "Bitwarden kann Zwei-Faktor-Authentifizierungsscodes speichern und ausfüllen. Kopiere und füge den Schlüssel in dieses Feld ein." + }, + "totpHelperWithCapture": { + "message": "Bitwarden kann Zwei-Faktor-Authentifizierungsscodes speichern und ausfüllen. Wähle das Kamerasymbol aus, um einen Screenshot des Authenticator-QR-Codes dieser Website zu machen oder kopiere und füge den Schlüssel in dieses Feld ein." + }, "copyTOTP": { "message": "Authentifizierungsschlüssel kopieren (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Ihre Login-Sitzung ist abgelaufen." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Bist du sicher, dass du dich abmelden willst?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Ihr neues Master-Passwort entspricht nicht den Anforderungen der Richtlinie." }, - "receiveMarketingEmails": { - "message": "Erhalte E-Mails von Bitwarden für Ankündigungen, Ratschläge und Forschungsmöglichkeiten." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Deabonnieren" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Gerät wird vertraut" }, + "sendsNoItemsTitle": { + "message": "Keine aktiven Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Verwende Send, um verschlüsselte Informationen sicher mit anderen zu teilen.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Eingabe ist erforderlich." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Fehler beim Verbinden mit dem Duo-Dienst. Verwende eine andere Zwei-Faktor-Authentifizierungsmethode oder kontaktiere Duo für Hilfe." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Starte DUO und folge den Schritten, um die Anmeldung zu abzuschließen." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Zugangsdaten" + }, + "authenticatorKey": { + "message": "Authenticator-Schlüssel" + }, "cardDetails": { "message": "Kartendetails" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Konto hinzufügen" + }, + "loading": { + "message": "Wird geladen" + }, + "assign": { + "message": "Zuweisen" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Nur Organisationsmitglieder mit Zugriff auf diese Sammlungen können die Einträge sehen." + }, + "bulkCollectionAssignmentWarning": { + "message": "Du hast $TOTAL_COUNT$ Einträge ausgewählt. Du kannst $READONLY_COUNT$ der Einträge nicht aktualisieren, da du keine Bearbeitungsrechte hast.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Feld hinzufügen" + }, + "add": { + "message": "Hinzufügen" + }, + "fieldType": { + "message": "Feldtyp" + }, + "fieldLabel": { + "message": "Feldbezeichnung" + }, + "textHelpText": { + "message": "Verwende Textfelder für Daten wie Sicherheitsfragen" + }, + "hiddenHelpText": { + "message": "Verwende versteckte Felder für vertrauliche Daten wie ein Passwort" + }, + "checkBoxHelpText": { + "message": "Verwende Kontrollkästchen, wenn du das Kontrollkästchen eines Formulars automatisch ausfüllen möchtest, wie eine Erinnerungs-E-Mail" + }, + "linkedHelpText": { + "message": "Verwende ein verknüpftes Feld, wenn du Probleme mit dem automatischen Ausfüllen einer bestimmten Website hast." + }, + "linkedLabelHelpText": { + "message": "Gib die HTML-ID, den Namen oder das aria-label des Feldes ein. Verwende alternativ einen Platzhalter." + }, + "editField": { + "message": "Feld bearbeiten" + }, + "editFieldLabel": { + "message": "$LABEL$ bearbeiten", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "$LABEL$ löschen", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ hinzugefügt", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "$LABEL$ umsortieren. Verwende die Pfeiltasten, um das Element nach oben oder nach unten zu verschieben.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ nach oben verschoben, Position $INDEX$ von $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Zu zuweisende Sammlungen auswählen" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ werden dauerhaft an die ausgewählte Organisation übertragen. Du wirst diese Einträge nicht mehr besitzen.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ werden dauerhaft an $ORG$ übertragen. Du wirst diese Einträge nicht mehr besitzen.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Sammlungen erfolgreich zugewiesen" + }, + "nothingSelected": { + "message": "Du hast nichts ausgewählt." + }, + "movedItemsToOrg": { + "message": "Ausgewählte Einträge in $ORGNAME$ verschoben", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ nach unten verschoben, Position $INDEX$ von $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index e60f2b61d16..10422951202 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden Διαχειριστής Κωδικών Πρόσβασης", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "message": "Το Bitwarden ασφαλίζει παντού και εύκολα τους κωδικούς, τα κλειδιά πρόσβασης και τις ευαίσθητες πληροφορίες σας", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -17,10 +17,10 @@ "message": "Δημιουργία λογαριασμού" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Ορίστε έναν ισχυρό κωδικό πρόσβασης" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "Ολοκληρώστε τη δημιουργία του λογαριασμού σας ορίζοντας έναν κωδικό πρόσβασης" }, "login": { "message": "Σύνδεση" @@ -38,10 +38,10 @@ "message": "Υποβολή" }, "emailAddress": { - "message": "Διεύθυνση email" + "message": "Διεύθυνση ηλεκτρονικού ταχυδρομείου" }, "masterPass": { - "message": "Κύριος κωδικός" + "message": "Κύριος κωδικός πρόσβασης" }, "masterPassDesc": { "message": "Ο κύριος κωδικός είναι ο κωδικός που χρησιμοποιείτε για την πρόσβαση στο vault σας. Είναι πολύ σημαντικό να μην ξεχάσετε τον κύριο κωδικό. Δεν υπάρχει τρόπος να ανακτήσετε τον κωδικό σε περίπτωση που τον ξεχάσετε." @@ -50,7 +50,7 @@ "message": "Η υπόδειξη του κύριου κωδικού μπορεί να σας βοηθήσει να θυμηθείτε τον κωδικό σας, σε περίπτωση που τον ξεχάσετε." }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "Αν ξεχάσετε τον κωδικό πρόσβασής σας, η υπενθύμιση του μπορεί να σταλεί στο ηλεκτρονικό ταχυδρομείο σας. $CURRENT$/$MAXIMUM$ μέγιστοι χαρακτήρες.", "placeholders": { "current": { "content": "$1", @@ -66,7 +66,7 @@ "message": "Πληκτρολογήστε ξανά τον Κύριο Κωδικό" }, "masterPassHint": { - "message": "Υπόδειξη Κύριου Κωδικού (προαιρετικό)" + "message": "Υπόδειξη κύριου κωδικού πρόσβασης (προαιρετικό)" }, "tab": { "message": "Καρτέλα" @@ -75,10 +75,10 @@ "message": "Vault" }, "myVault": { - "message": "Το Vault μου" + "message": "Το θησαυ/κιό μου" }, "allVaults": { - "message": "Όλα τα Vaults" + "message": "Όλα τα θησαυ/κια" }, "tools": { "message": "Εργαλεία" @@ -90,22 +90,22 @@ "message": "Τρέχουσα καρτέλα" }, "copyPassword": { - "message": "Αντιγραφή Κωδικού" + "message": "Αντιγραφή κωδικού πρόσβασης" }, "copyNote": { - "message": "Αντιγραφή Σημείωσης" + "message": "Αντιγραφή σημείωσης" }, "copyUri": { "message": "Αντιγραφή URI" }, "copyUsername": { - "message": "Αντιγραφή Ονόματος Χρήστη" + "message": "Αντιγραφή ονόματος χρήστη" }, "copyNumber": { - "message": "Αντιγραφή Αριθμού" + "message": "Αντιγραφή αριθμού" }, "copySecurityCode": { - "message": "Αντιγραφή Κωδικού Ασφαλείας" + "message": "Αντιγραφή κωδικού ασφαλείας" }, "autoFill": { "message": "Αυτόματη συμπλήρωση" @@ -120,7 +120,7 @@ "message": "Αυτόματη συμπλήρωση ταυτότητας" }, "generatePasswordCopied": { - "message": "Δημιουργία Κωδικού (αντιγράφηκε)" + "message": "Δημιουργία κωδικού πρόσβασης (αντιγράφηκε)" }, "copyElementIdentifier": { "message": "Αντιγραφή ονόματος προσαρμοσμένου πεδίου" @@ -135,7 +135,7 @@ "message": "Δεν υπάρχουν ταυτότητες" }, "addLoginMenu": { - "message": "Προσθήκη Στοιχείων Σύνδεσης" + "message": "Προσθήκη σύνδεσης" }, "addCardMenu": { "message": "Προσθήκη κάρτας" @@ -153,13 +153,13 @@ "message": "Δεν υπάρχουν διαθέσιμες συνδέσεις για την αυτόματη συμπλήρωση, στην τρέχουσα καρτέλα του προγράμματος περιήγησης." }, "addLogin": { - "message": "Προσθήκη Στοιχείων Σύνδεσης" + "message": "Προσθήκη μίας σύνδεσης" }, "addItem": { - "message": "Προσθήκη Αντικειμένου" + "message": "Προσθήκη αντικειμένου" }, "passwordHint": { - "message": "Υπόδειξη Κωδικού" + "message": "Υπόδειξη κωδικού πρόσβασης" }, "enterEmailToGetHint": { "message": "Εισαγάγετε τη διεύθυνση email του λογαριασμού σας, για να λάβετε την υπόδειξη του κύριου κωδικού." @@ -174,13 +174,13 @@ "message": "Στείλτε έναν κωδικό επαλήθευσης στο email σας" }, "sendCode": { - "message": "Αποστολή Κωδικού" + "message": "Αποστολή κωδικού" }, "codeSent": { "message": "Ο κωδικός στάλθηκε" }, "verificationCode": { - "message": "Κωδικός Επαλήθευσης" + "message": "Κωδικός επαλήθευσης" }, "confirmIdentity": { "message": "Επιβεβαιώστε την ταυτότητα σας για να συνεχίσετε." @@ -189,25 +189,25 @@ "message": "Αλλαγή Κύριου Κωδικού" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Συνέχεια στη διαδικτυακή εφαρμογή;" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "Εξερευνήστε περισσότερες δυνατότητες του λογαριασμού σας Bitwarden στη διαδικτυακή εφαρμογή." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "Συνεχίστε στο Κέντρο Βοήθειας;" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "Μάθετε περισσότερα για το πώς να χρησιμοποιήσετε το Bitwarden στο Κέντρο Βοήθειας." }, "continueToBrowserExtensionStore": { - "message": "Continue to browser extension store?" + "message": "Συνεχίστε στο κατάστημα επεκτάσεων περιηγητή;" }, "continueToBrowserExtensionStoreDesc": { - "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + "message": "Βοηθήστε άλλους να μάθουν αν το Bitwarden είναι κατάλληλο για αυτούς. Επισκεφθείτε το κατάστημα επεκτάσεων του περιηγητή σας και αφήστε τώρα μια βαθμολογία." }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Μπορείτε να αλλάξετε τον κύριο κωδικό πρόσβασής σας στη διαδικτυακή εφαρμογή του Bitwarden." }, "fingerprintPhrase": { "message": "Φράση Δακτυλικών Αποτυπωμάτων", @@ -224,43 +224,43 @@ "message": "Αποσύνδεση" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Σχετικά με το Bitwarden" }, "about": { "message": "Σχετικά" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Περισσότερα από το Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "Συνέχεια στο bitwarden.com;" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "Bitwarden για Επιχειρήσεις" }, "bitwardenAuthenticator": { - "message": "Bitwarden Authenticator" + "message": "Αυθεντικοποιητής Bitwarden" }, "continueToAuthenticatorPageDesc": { - "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website" + "message": "Ο αυθεντικοποιητής Bitwarden σάς επιτρέπει να αποθηκεύετε τα κλειδιά αυθεντικοποίησης και να δημιουργείτε κωδικούς TOTP για ροές επαλήθευσης δύο βημάτων. Μάθετε περισσότερα στην ιστοσελίδα bitwarden.com" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Διαχειριστής Bitwarden Secrets" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "Αποθηκεύστε και μοιραστείτε μυστικά προγραμματιστών με τον Διαχειριστή Bitwarden Secrets. Μάθετε περισσότερα στον ιστότοπο bitwarden.com." }, "passwordlessDotDev": { "message": "Passwordless.dev" }, "continueToPasswordlessDotDevPageDesc": { - "message": "Create smooth and secure login experiences free from traditional passwords with Passwordless.dev. Learn more on the bitwarden.com website." + "message": "Δημιουργήστε ομαλές και ασφαλείς εμπειρίες σύνδεσης δωρεάν από παραδοσιακούς κωδικούς πρόσβασης με το Passwordless.dev. Μάθετε περισσότερα στην ιστοσελίδα bitwarden.com." }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "Δωρεάν Bitwarden για Οικογένειες" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "Δικαιούστε το Δωρεάν Bitwarden για Οικογένειες. Εξαργυρώστε αυτή την προσφορά σήμερα στην διαδικτυακή εφαρμογή." }, "version": { "message": "Έκδοση" @@ -278,10 +278,10 @@ "message": "Όνομα" }, "editFolder": { - "message": "Επεξεργασία Φακέλου" + "message": "Επεξεργασία φακέλου" }, "deleteFolder": { - "message": "Διαγραφή Φακέλου" + "message": "Διαγραφή φακέλου" }, "folders": { "message": "Φάκελοι" @@ -293,25 +293,25 @@ "message": "Βοήθεια & Σχόλια" }, "helpCenter": { - "message": "Κέντρο βοήθειας Bitwarden" + "message": "Κέντρο Βοήθειας Bitwarden" }, "communityForums": { - "message": "Εξερευνήστε τα φόρουμ της κοινότητας του Bitwarden" + "message": "Εξερευνήστε τα φόρουμ κοινότητας Bitwarden" }, "contactSupport": { - "message": "Επικοινωνία με την υποστήριξη Bitwarden" + "message": "Επικοινωνία με την υποστήριξη του Bitwarden" }, "sync": { "message": "Συγχρονισμός" }, "syncVaultNow": { - "message": "Συγχρονισμός λίστας" + "message": "Συγχρονισμός θησαυ/κιου τώρα" }, "lastSync": { "message": "Τελευταίος συγχρονισμός:" }, "passGen": { - "message": "Γεννήτρια Κωδικού" + "message": "Γεννήτρια κωδικού πρόσβασης" }, "generator": { "message": "Γεννήτρια", @@ -321,7 +321,7 @@ "message": "Δημιουργήστε αυτόματα ισχυρούς και μοναδικούς κωδικούς πρόσβασης για τις συνδέσεις σας." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Διαδικτυακή εφαρμογή Bitwarden" }, "importItems": { "message": "Εισαγωγή στοιχείων" @@ -330,10 +330,10 @@ "message": "Επιλογή" }, "generatePassword": { - "message": "Δημιουργία Κωδικού" + "message": "Δημιουργία κωδικού πρόσβασης" }, "regeneratePassword": { - "message": "Επαναδημιουργία Κωδικού" + "message": "Επαναδημιουργία κωδικού πρόσβασης" }, "options": { "message": "Επιλογές" @@ -342,7 +342,7 @@ "message": "Μήκος" }, "passwordMinLength": { - "message": "Minimum password length" + "message": "Ελάχιστο μήκος κωδικού πρόσβασης" }, "uppercase": { "message": "Κεφαλαία (A-Z)" @@ -354,29 +354,29 @@ "message": "Αριθμοί (0-9)" }, "specialCharacters": { - "message": "Ειδικοί Χαρακτήρες (!@#$%^&*)" + "message": "Ειδικοί χαρακτήρες (!@#$%^&*)" }, "numWords": { - "message": "Αριθμός Λέξεων" + "message": "Αριθμός λέξεων" }, "wordSeparator": { - "message": "Διαχωριστής Λέξεων" + "message": "Διαχωριστής λέξεων" }, "capitalize": { "message": "Κεφαλαία", "description": "Make the first letter of a work uppercase." }, "includeNumber": { - "message": "Συμπερίληψη Αριθμών" + "message": "Συμπερίληψη αριθμών" }, "minNumbers": { - "message": "Ελάχιστα Αριθμητικά Ψηφία" + "message": "Ελάχιστα αριθμητικά ψηφία" }, "minSpecial": { - "message": "Ελάχιστο Ειδικών Χαρακτήρων" + "message": "Ελάχιστοι ειδικοί χαρακτήρες" }, "avoidAmbChar": { - "message": "Αποφυγή Αμφιλεγόμενων Χαρακτήρων" + "message": "Αποφυγή αμφιλεγόμενων χαρακτήρων" }, "searchVault": { "message": "Αναζήτηση στο vault" @@ -391,7 +391,7 @@ "message": "Δεν υπάρχουν στοιχεία στη λίστα." }, "itemInformation": { - "message": "Πληροφορίες Αντικειμένου" + "message": "Πληροφορίες αντικειμένου" }, "username": { "message": "Όνομα Χρήστη" @@ -400,7 +400,7 @@ "message": "Κωδικός" }, "totp": { - "message": "Authenticator secret" + "message": "Μυστικό αυθεντικοποίησης" }, "passphrase": { "message": "Συνθηματικό" @@ -409,13 +409,13 @@ "message": "Αγαπημένο" }, "unfavorite": { - "message": "Unfavorite" + "message": "Αφαίρεση από τα αγαπημένα" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "Το αντικείμενο προστέθηκε στα αγαπημένα" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "Το αντικείμενο αφαιρέθηκε από τα αγαπημένα" }, "notes": { "message": "Σημειώσεις" @@ -424,28 +424,28 @@ "message": "Σημείωση" }, "editItem": { - "message": "Επεξεργασία Αντικειμένου" + "message": "Επεξεργασία αντικειμένου" }, "folder": { "message": "Φάκελος" }, "deleteItem": { - "message": "Διαγραφή Στοιχείου" + "message": "Διαγραφή στοιχείου" }, "viewItem": { - "message": "Προβολή Αντικειμένου" + "message": "Προβολή αντικειμένου" }, "launch": { "message": "Εκκίνηση" }, "launchWebsite": { - "message": "Launch website" + "message": "Εκκίνηση ιστοσελίδας" }, "website": { "message": "Ιστοσελίδα" }, "toggleVisibility": { - "message": "Εναλλαγή Ορατότητας" + "message": "Εναλλαγή ορατότητας" }, "manage": { "message": "Διαχείριση" @@ -454,19 +454,19 @@ "message": "Άλλες" }, "unlockMethods": { - "message": "Unlock options" + "message": "Επιλογές ξεκλειδώματος" }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Ρυθμίστε μια μέθοδο ξεκλειδώματος για να αλλάξετε την ενέργεια χρονικού ορίου θησαυ/κιου." + "message": "Ορίστε μια μέθοδο ξεκλειδώματος για να αλλάξετε την ενέργεια χρονικού ορίου λήξης θησαυ/κίου." }, "unlockMethodNeeded": { - "message": "Set up an unlock method in Settings" + "message": "Ορίστε μία μέθοδο ξεκλειδώματος στις Ρυθμίσεις" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Χρονικό όριο λήξης συνεδρίας" }, "otherOptions": { - "message": "Other options" + "message": "Άλλες επιλογές" }, "rateExtension": { "message": "Βαθμολογήστε την επέκταση" @@ -509,7 +509,7 @@ "message": "Κλείδωμα Τώρα" }, "lockAll": { - "message": "Lock all" + "message": "Κλείδωμα όλων" }, "immediately": { "message": "Άμεσα" @@ -557,16 +557,16 @@ "message": "Ασφάλεια" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "Επιβεβαίωση κύριου κωδικού πρόσβασης" }, "masterPassword": { - "message": "Master password" + "message": "Κύριος κωδικός πρόσβασης" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "Ο κύριος κωδικός πρόσβασής σας δεν μπορεί να ανακτηθεί αν τον ξεχάσετε!" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Υπόδειξη κύριου κωδικού πρόσβασης" }, "errorOccurred": { "message": "Παρουσιάστηκε σφάλμα" @@ -584,7 +584,7 @@ "message": "Απαιτείται ξανά ο κύριος κωδικός πρόσβασης." }, "masterPasswordMinlength": { - "message": "Ο κύριος κωδικός πρέπει να έχει μήκος τουλάχιστον $VALUE$ χαρακτήρες.", + "message": "Ο κύριος κωδικός πρόσβασης πρέπει να έχει τουλάχιστον $VALUE$ χαρακτήρες μήκος.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -600,10 +600,10 @@ "message": "Ο λογαριασμός σας έχει δημιουργηθεί! Τώρα μπορείτε να συνδεθείτε." }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Έχετε συνδεθεί επιτυχώς" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Μπορείτε να κλείσετε αυτό το παράθυρο" }, "masterPassSent": { "message": "Σας στείλαμε ένα email με την υπόδειξη του κύριου κωδικού." @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Απαιτείται ο κωδικός επαλήθευσης." }, + "webauthnCancelOrTimeout": { + "message": "Η αυθεντικοποίηση ακυρώθηκε ή διήρκησε πολύ ώρα. Παρακαλώ προσπαθήστε ξανά." + }, "invalidVerificationCode": { "message": "Μη έγκυρος κωδικός επαλήθευσης" }, @@ -628,26 +631,50 @@ "message": "Δεν είναι δυνατή η αυτόματη συμπλήρωση του επιλεγμένου στοιχείου σε αυτήν τη σελίδα. Αντιγράψτε και επικολλήστε τις πληροφορίες." }, "totpCaptureError": { - "message": "Unable to scan QR code from the current webpage" + "message": "Αδυναμία σάρωσης του κωδικού QR από την τρέχουσα ιστοσελίδα" }, "totpCaptureSuccess": { - "message": "Authenticator key added" + "message": "Το κλειδι αυθεντικοποίησης προστέθηκε" }, "totpCapture": { - "message": "Scan authenticator QR code from current webpage" + "message": "Σάρωση κωδικού QR αυθεντικοποιητή από την τρέχουσα ιστοσελίδα" + }, + "totpHelperTitle": { + "message": "Κάντε την επαλήθευση δύο βημάτων απρόσκοπτη" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "Αντιγραφή κλειδιού Αυθεντικοποιητή (TOTP)" }, "loggedOut": { "message": "Αποσυνδεθήκατε" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Έχετε αποσυνδεθεί από τον λογαριασμό σας." }, "loginExpired": { "message": "Η περίοδος σύνδεσης σας έχει λήξει." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Είστε βέβαιοι ότι θέλετε να αποσυνδεθείτε;" }, @@ -667,19 +694,19 @@ "message": "Προστέθηκε φάκελος" }, "twoStepLoginConfirmation": { - "message": "Η σύνδεση σε δύο βήματα καθιστά πιο ασφαλή τον λογαριασμό σας, απαιτώντας να επαληθεύσετε τα στοιχεία σας με μια άλλη συσκευή, όπως κλειδί ασφαλείας, εφαρμογή επαλήθευσης, μήνυμα SMS, τηλεφωνική κλήση ή email. Μπορείτε να ενεργοποιήσετε τη σύνδεση σε δύο βήματα στο bitwarden.com. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;" + "message": "Η σύνδεση δύο βημάτων καθιστά πιο ασφαλή τον λογαριασμό σας, απαιτώντας να επαληθεύσετε τη συνδεσή σας με μια άλλη συσκευή, όπως ένα κλειδί ασφαλείας, μία εφαρμογή επαλήθευσης, ένα μήνυμα SMS, μία τηλεφωνική κλήση ή ένα μήνυμα ηλεκτρονικου ταχυδρομείου. Μπορείτε να ενεργοποιήσετε τη σύνδεση δύο βημάτων στο διαδικτυακό θυσαυ/κιο bitwarden.com. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;" }, "editedFolder": { - "message": "Επεξεργασμένος φάκελος" + "message": "Ο φάκελος αποθηκεύτηκε" }, "deleteFolderConfirmation": { "message": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν το φάκελο;" }, "deletedFolder": { - "message": "Διεγραμμένος φάκελος" + "message": "Ο φάκελος διαγράφηκε" }, "gettingStartedTutorial": { - "message": "Οδηγός Εκμάθησης" + "message": "Οδηγός εκμάθησης" }, "gettingStartedTutorialVideo": { "message": "Παρακολουθήστε τον οδηγό εκμάθησης μας, για να μάθετε πώς μπορείτε να αξιοποιήσετε στο έπακρο την επέκταση του προγράμματος περιήγησης." @@ -713,13 +740,13 @@ "message": "Το στοιχείο προστέθηκε" }, "editedItem": { - "message": "Επεξεργασμένο στοιχείο" + "message": "Το αντικείμενο αποθηκεύτηκε" }, "deleteItemConfirmation": { "message": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το στοιχείο;" }, "deletedItem": { - "message": "Διαγραμμένο στοιχείο" + "message": "Το αντικείμενο μετακινήθηκε στον κάδο απορριμάτων" }, "overwritePassword": { "message": "Αντικατάσταση κωδικού πρόσβασης" @@ -753,7 +780,7 @@ "message": "Η \"Προσθήκη Ειδοποίησης Σύνδεσης\" σας προτρέπει αυτόματα να αποθηκεύσετε νέες συνδέσεις στο vault σας κάθε φορά που θα συνδεθείτε για πρώτη φορά." }, "addLoginNotificationDescAlt": { - "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." + "message": "Ζητήστε να προσθέσετε ένα αντικείμενο αν δε βρεθεί στο θησαυ/κιό σας. Ισχύει για όλους τους συνδεδεμένους λογαριασμούς." }, "showCardsCurrentTab": { "message": "Εμφάνιση καρτών στη σελίδα Καρτέλας" @@ -788,10 +815,10 @@ "message": "Ζητήστε να ενημερώσετε τον κωδικό πρόσβασης μιας σύνδεσης όταν εντοπιστεί μια αλλαγή σε μια ιστοσελίδα." }, "changedPasswordNotificationDescAlt": { - "message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts." + "message": "Ρώτησε για να ενημερώσεις τον κωδικό πρόσβασης μιας σύνδεσης όταν εντοπιστεί μια αλλαγή σε έναν ιστότοπο. Ισχύει για όλους τους συνδεδεμένους λογαριασμούς." }, "enableUsePasskeys": { - "message": "Ask to save and use passkeys" + "message": "Ρώτησε για αποθήκευση και χρήση κλειδιών πρόσβασης" }, "usePasskeysDesc": { "message": "Ask to save new passkeys or log in with passkeys stored in your vault. Applies to all logged in accounts." @@ -809,7 +836,7 @@ "message": "Ξεκλείδωμα" }, "additionalOptions": { - "message": "Additional options" + "message": "Πρόσθετες επιλογές" }, "enableContextMenuItem": { "message": "Εμφάνιση επιλογών μενού περιβάλλοντος" @@ -849,7 +876,7 @@ "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, "exportFrom": { - "message": "Export from" + "message": "Εξαγωγή από" }, "exportVault": { "message": "Εξαγωγή Vault" @@ -858,35 +885,35 @@ "message": "Μορφή αρχείου" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "Αυτή η εξαγωγή αρχείου θα προστατεύεται με κωδικό πρόσβασης και θα απαιτείται ο κωδικός πρόσβασης του αρχείου για αποκρυπτογράφηση." }, "filePassword": { - "message": "File password" + "message": "Κωδικός πρόσβασης αρχείου" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "Αυτός ο κωδικός πρόσβασης θα χρησιμοποιηθεί για την εξαγωγή και εισαγωγή αυτού του αρχείου" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "Χρησιμοποιήστε το κλειδί κρυπτογράφησης του λογαριασμού σας, που προέρχεται από το όνομα χρήστη και τον Κύριο Κωδικό Πρόσβασης του λογαριασμού σας, για να κρυπτογραφήσετε την εξαγωγή και να περιορίσετε την εισαγωγή μόνο στον τρέχοντα λογαριασμό Bitwarden." }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "Ορίστε έναν κωδικό πρόσβασης αρχείου για να κρυπτογραφήσετε την εξαγωγή, και να την εισάγετε σε οποιονδήποτε λογαριασμό Bitwarden χρησιμοποιώντας τον κωδικό πρόσβασης για αποκρυπτογράφηση." }, "exportTypeHeading": { - "message": "Export type" + "message": "Τύπος εξαγωγής" }, "accountRestricted": { - "message": "Account restricted" + "message": "Ο λογαριασμός περιορίστηκε" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "“Κωδικός πρόσβασης αρχείου” και “Επιβεβαίωση κωδικού πρόσβασης αρχείου“ δεν ταιριάζουν." }, "warning": { "message": "ΠΡΟΕΙΔΟΠΟΙΗΣΗ", "description": "WARNING (should stay in capitalized letters if the language permits)" }, "confirmVaultExport": { - "message": "Επιβεβαίωση εξαγωγής vault" + "message": "Επιβεβαίωση εξαγωγής θησαυ/κίου" }, "exportWarningDesc": { "message": "Αυτή η εξαγωγή περιέχει τα δεδομένα σε μη κρυπτογραφημένη μορφή. Δεν πρέπει να αποθηκεύετε ή να στείλετε το εξαγόμενο αρχείο μέσω μη ασφαλών τρόπων (όπως μέσω email). Διαγράψτε το αμέσως μόλις τελειώσετε με τη χρήση του." @@ -974,7 +1001,7 @@ "message": "Μη διαθέσιμο χαρακτηριστικό" }, "encryptionKeyMigrationRequired": { - "message": "Απαιτείται μεταφορά κλειδιού κρυπτογράφησης. Παρακαλούμε συνδεθείτε μέσω του διαδικτυακού θησαυ/κιου για να ενημερώσετε το κλειδί κρυπτογράφησης." + "message": "Απαιτείται μεταφορά κλειδιού κρυπτογράφησης. Παρακαλούμε συνδεθείτε μέσω του διαδικτυακού θησαυ/κίου για να ενημερώσετε το κλειδί κρυπτογράφησης." }, "premiumMembership": { "message": "Συνδρομή Premium" @@ -989,10 +1016,10 @@ "message": "Ανανέωση συνδρομής" }, "premiumNotCurrentMember": { - "message": "Δεν είστε μέλος Premium." + "message": "Δεν είστε Premium μέλος αυτήν τη στιγμή." }, "premiumSignUpAndGet": { - "message": "Εγγραφείτε για συνδρομή Premium και λάβετε:" + "message": "Εγγραφείτε για μία συνδρομή Premium και λάβετε:" }, "ppremiumSignUpStorage": { "message": "1 GB κρυπτογραφημένο αποθηκευτικό χώρο για συνημμένα αρχεία." @@ -1010,7 +1037,7 @@ "message": "Προτεραιότητα υποστήριξης πελατών." }, "ppremiumSignUpFuture": { - "message": "Όλα τα μελλοντικά χαρακτηριστικά Premium. Έρχονται περισσότερα σύντομα!" + "message": "Όλες οι μελλοντικές λειτουργίες Premium. Έρχονται περισσότερα σύντομα!" }, "premiumPurchase": { "message": "Αγορά Premium έκδοσης" @@ -1049,7 +1076,7 @@ "message": "Απαιτείται έκδοση Premium" }, "premiumRequiredDesc": { - "message": "Για να χρησιμοποιήσετε αυτή τη λειτουργία, απαιτείται έκδοση Premium." + "message": "Για να χρησιμοποιήσετε αυτή τη λειτουργία, απαιτείται συνδρομή Premium." }, "enterVerificationCodeApp": { "message": "Εισάγετε τον 6ψήφιο κωδικό από την εφαρμογή επαλήθευσης." @@ -1115,20 +1142,20 @@ "message": "Κωδικός ανάκτησης" }, "authenticatorAppTitle": { - "message": "Εφαρμογή ελέγχου ταυτότητας" + "message": "Εφαρμογή αυθεντικοποίησης" }, "authenticatorAppDescV2": { "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "Κλειδί Ασφαλείας Yubico OTP" }, "yubiKeyDesc": { "message": "Χρησιμοποιήστε ένα YubiKey για να αποκτήσετε πρόσβαση στο λογαριασμό σας. Λειτουργεί με συσκευές σειράς YubiKey 4, 4 Nano, 4C και συσκευές NEO." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "Εισάγετε έναν κωδικό που δημιουργήθηκε από το Duo Security.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1139,16 +1166,16 @@ "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "Χρησιμοποιήστε οποιοδήποτε κλειδί ασφαλείας συμβατό με το WebAuthn για να αποκτήσετε πρόσβαση στο λογαριασμό σας." + "message": "Χρησιμοποιήστε οποιοδήποτε κλειδί ασφαλείας συμβατό με το WebAuthn για να αποκτήσετε πρόσβαση στον λογαριασμό σας." }, "emailTitle": { "message": "Email" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Εισάγετε έναν κωδικό που σας στάλθηκε στο ηλεκτρονικό ταχυδρομείο σας." }, "selfHostedEnvironment": { - "message": "Αυτο-φιλοξενούμενο περιβάλλον" + "message": "Αυτο-εξυπηρετούμενο περιβάλλον" }, "selfHostedEnvironmentFooter": { "message": "Καθορίστε τη βασική διεύθυνση URL, της εγκατάστασης του Bitwarden που φιλοξενείται στο χώρο σας." @@ -1157,10 +1184,10 @@ "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "Για προχωρημένη παραμετροποίηση, μπορείτε να ορίσετε ανεξάρτητα το βασικό URL κάθε υπηρεσίας." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "Πρέπει να προσθέσετε είτε το βασικό URL του διακομιστή ή τουλάχιστον ένα προσαρμοσμένο περιβάλλον." }, "customEnvironment": { "message": "Προσαρμοσμένο περιβάλλον" @@ -1172,38 +1199,38 @@ "message": "URL Διακομιστή" }, "apiUrl": { - "message": "URL Διακομιστή API" + "message": "URL διακομιστή API" }, "webVaultUrl": { "message": "Web Vault Server URL" }, "identityUrl": { - "message": "URL Ταυτότητας Διακομιστή" + "message": "URL διακομιστή ταυτότητας" }, "notificationsUrl": { - "message": "Ειδοποιήσεις Διεύθυνσης URL Διακομιστή" + "message": "URL διακομιστή ειδοποιήσεων" }, "iconsUrl": { - "message": "Εικονίδια διακομιστή URL" + "message": "URL διακομιστή εικονιδίων" }, "environmentSaved": { - "message": "Οι διευθύνσεις URL περιβάλλοντος έχουν αποθηκευτεί." + "message": "Τα URL περιβάλλοντος έχουν αποθηκευτεί" }, "showAutoFillMenuOnFormFields": { - "message": "Show auto-fill menu on form fields", + "message": "Εμφάνιση μενού αυτόματης συμπλήρωσης στα πεδία της φόρμας", "description": "Represents the message for allowing the user to enable the auto-fill overlay" }, "showAutoFillMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "Ισχύει για όλους τους συνδεδεμένους λογαριασμούς." }, "turnOffBrowserBuiltInPasswordManagerSettings": { "message": "Turn off your browser’s built in password manager settings to avoid conflicts." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { - "message": "Edit browser settings." + "message": "Επεξεργαστείτε τις ρυθμίσεις του περιηγητή." }, "autofillOverlayVisibilityOff": { - "message": "Off", + "message": "Ανενεργό", "description": "Overlay setting select option for disabling autofill overlay" }, "autofillOverlayVisibilityOnFieldFocus": { @@ -1211,7 +1238,7 @@ "description": "Overlay appearance select option for showing the field on focus of the input element" }, "autofillOverlayVisibilityOnButtonClick": { - "message": "When auto-fill icon is selected", + "message": "Όταν το εικονίδιο αυτόματης συμπλήρωσης είναι επιλεγμένο", "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoad": { @@ -1260,16 +1287,16 @@ "message": "Κλειδώστε το vault" }, "customFields": { - "message": "Προσαρμοσμένα Πεδία" + "message": "Προσαρμοσμένα πεδία" }, "copyValue": { - "message": "Αντιγραφή Τιμής" + "message": "Αντιγραφή τιμής" }, "value": { "message": "Τιμή" }, "newCustomField": { - "message": "Νέο Προσαρμοσμένο Πεδίο" + "message": "Νέο προσαρμοσμένο πεδίο" }, "dragToSort": { "message": "Σύρετε για ταξινόμηση" @@ -1304,7 +1331,7 @@ "message": "Εμφάνιση μιας αναγνωρίσιμης εικόνας δίπλα σε κάθε σύνδεση." }, "faviconDescAlt": { - "message": "Show a recognizable image next to each login. Applies to all logged in accounts." + "message": "Εμφάνιση μιας αναγνωρίσιμης εικόνας δίπλα σε κάθε σύνδεση. Ισχύει για όλους τους συνδεδεμένους λογαριασμούς." }, "enableBadgeCounter": { "message": "Εμφάνιση μετρητή εμβλημάτων" @@ -1313,7 +1340,7 @@ "message": "Υποδείξτε πόσες συνδέσεις έχετε για την τρέχουσα ιστοσελίδα." }, "cardholderName": { - "message": "Όνομα κατόχου της κάρτας" + "message": "Όνομα κατόχου κάρτας" }, "number": { "message": "Αριθμός" @@ -1322,7 +1349,7 @@ "message": "Επωνυμία" }, "expirationMonth": { - "message": "Μήνας Λήξης" + "message": "Μήνας λήξης" }, "expirationYear": { "message": "Έτος λήξης" @@ -1367,7 +1394,7 @@ "message": "Δεκέμβριος" }, "securityCode": { - "message": "Κωδικός Ασφαλείας" + "message": "Κωδικός ασφαλείας" }, "ex": { "message": "πχ." @@ -1388,7 +1415,7 @@ "message": "Dr" }, "mx": { - "message": "Κ@" + "message": "Κ" }, "firstName": { "message": "Όνομα" @@ -1403,7 +1430,7 @@ "message": "Ονοματεπώνυμο" }, "identityName": { - "message": "Όνομα Ταυτότητας" + "message": "Όνομα ταυτότητας" }, "company": { "message": "Εταιρεία" @@ -1412,10 +1439,10 @@ "message": "ΑΜΚΑ" }, "passportNumber": { - "message": "Αριθμός Διαβατηρίου" + "message": "Αριθμός διαβατηρίου" }, "licenseNumber": { - "message": "Αριθμός Άδειας" + "message": "Αριθμός άδειας" }, "email": { "message": "Email" @@ -1442,7 +1469,7 @@ "message": "Περιοχή / Νομός" }, "zipPostalCode": { - "message": "Ταχυδρομικός Κώδικας" + "message": "Ταχυδρομικός κώδικας" }, "country": { "message": "Χώρα" @@ -1457,7 +1484,7 @@ "message": "Συνδέσεις" }, "typeSecureNote": { - "message": "Ασφαλής Σημείωση" + "message": "Ασφαλής σημείωση" }, "typeCard": { "message": "Κάρτα" @@ -1466,7 +1493,7 @@ "message": "Ταυτότητα" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Νέα $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1475,7 +1502,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "Επεξεργασία $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1484,7 +1511,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "Εμφάνιση $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1493,7 +1520,7 @@ } }, "passwordHistory": { - "message": "Ιστορικό Κωδικού" + "message": "Ιστορικό κωδικού πρόσβασης" }, "back": { "message": "Πίσω" @@ -1502,7 +1529,7 @@ "message": "Συλλογές" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "$COUNT$ συλλογές", "placeholders": { "count": { "content": "$1", @@ -1529,7 +1556,7 @@ "message": "Συνδέσεις" }, "secureNotes": { - "message": "Ασφαλείς Σημειώσεις" + "message": "Ασφαλείς σημειώσεις" }, "clear": { "message": "Εκκαθάριση", @@ -1573,7 +1600,7 @@ "description": "A programming term, also known as 'RegEx'." }, "matchDetection": { - "message": "Εντοπισμός Αντιστοίχισης", + "message": "Εντοπισμός αντιστοίχισης", "description": "URI match detection for auto-fill." }, "defaultMatchDetection": { @@ -1581,7 +1608,7 @@ "description": "Default URI match detection for auto-fill." }, "toggleOptions": { - "message": "Επιλογές Εναλλαγής" + "message": "Επιλογές εναλλαγής" }, "toggleCurrentUris": { "message": "Εναλλαγή τρεχόντων URI", @@ -1599,7 +1626,7 @@ "message": "Τύποι" }, "allItems": { - "message": "Όλα τα στοιχεία" + "message": "Όλα τα αντικείμενα" }, "noPasswordsInList": { "message": "Δεν υπάρχουν κωδικοί στη λίστα." @@ -1619,7 +1646,7 @@ "description": "ex. Date this item was created" }, "datePasswordUpdated": { - "message": "Ο Κωδικός Ενημερώθηκε", + "message": "Ο κωδικός πρόσβασης ενημερώθηκε", "description": "ex. Date this password was updated" }, "neverLockWarning": { @@ -1672,7 +1699,7 @@ "message": "Μη έγκυρος κωδικός PIN." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "Πάρα πολλές άκυρες απόπειρες εισαγωγής PIN. Γίνεται αποσύνδεση." }, "unlockWithBiometrics": { "message": "Ξεκλείδωμα με βιομετρικά στοιχεία" @@ -1681,7 +1708,7 @@ "message": "Αναμονή επιβεβαίωσης από την επιφάνεια εργασίας" }, "awaitDesktopDesc": { - "message": "Παρακαλώ επιβεβαιώστε τη χρήση βιομετρικών στοιχείων στην εφαρμογή Bitwarden Desktop για να ενεργοποιήσετε τα βιομετρικά στοιχεία για το πρόγραμμα περιήγησης." + "message": "Παρακαλώ επιβεβαιώστε τη χρήση βιομετρικών στην εφαρμογή Bitwarden της επιφάνειας εργασίας για να ενεργοποιήσετε τα βιομετρικά για τον περιηγητή." }, "lockWithMasterPassOnRestart": { "message": "Κλείδωμα με κύριο κωδικό πρόσβασης στην επανεκκίνηση του προγράμματος περιήγησης" @@ -1690,7 +1717,7 @@ "message": "Πρέπει να επιλέξετε τουλάχιστον μία συλλογή." }, "cloneItem": { - "message": "Κλώνος Αντικειμένου" + "message": "Κλωνοποίηση αντικειμένου" }, "clone": { "message": "Κλώνος" @@ -1728,7 +1755,7 @@ "message": "Στοιχείο που έχει Ανακτηθεί" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Έχετε ήδη λογαριασμό;" }, "vaultTimeoutLogOutConfirmation": { "message": "Η αποσύνδεση θα καταργήσει όλη την πρόσβαση στο vault σας και απαιτεί online έλεγχο ταυτότητας μετά το χρονικό όριο λήξης. Είστε βέβαιοι ότι θέλετε να χρησιμοποιήσετε αυτήν τη ρύθμιση;" @@ -1740,7 +1767,7 @@ "message": "Αυτόματη συμπλήρωση και αποθήκευση" }, "fillAndSave": { - "message": "Fill and save" + "message": "Συμπλήρωση και αποθήκευση" }, "autoFillSuccessAndSavedUri": { "message": "Αυτόματη συμπλήρωση στοιχείου και αποθηκευμένο URI" @@ -1820,20 +1847,20 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Ο νέος κύριος κωδικός δεν πληροί τις απαιτήσεις πολιτικής." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "Ακύρωση συνδρομής" }, "atAnyTime": { - "message": "at any time." + "message": "οποιαδήποτε στιγμή." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Συνεχίζοντας, συμφωνείτε με" }, "and": { - "message": "and" + "message": "και" }, "acceptPolicies": { "message": "Επιλέγοντας αυτό το πλαίσιο, συμφωνείτε με τα εξής:" @@ -1854,10 +1881,10 @@ "message": "Οκ" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "Σφάλμα Ανανέωσης Διακριτικού Πρόσβασης" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Δε βρέθηκε κανένα διακριτικό ανανέωσης ή κλειδιά API. Παρακαλώ δοκιμάστε να αποσυνδεθείτε και να συνδεθείτε ξανά." }, "desktopSyncVerificationTitle": { "message": "Επιβεβαίωση συγχρονισμού επιφάνειας εργασίας" @@ -1908,10 +1935,10 @@ "message": "Τα βιομετρικά στοιχεία του προγράμματος περιήγησης δεν υποστηρίζονται σε αυτήν τη συσκευή." }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "Ο χρήστης κλειδώθηκε ή αποσυνδέθηκε" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "Παρακαλώ ξεκλειδώστε αυτόν τον χρήστη στην εφαρμογή επιφάνειας εργασίας και προσπαθήστε ξανά." }, "biometricsFailedTitle": { "message": "Ο βιομετρικός έλεγχος απέτυχε" @@ -1947,7 +1974,7 @@ "message": "Το Bitwarden δεν θα ζητήσει να αποθηκεύσετε τα στοιχεία σύνδεσης για αυτούς τους τομείς. Πρέπει να ανανεώσετε τη σελίδα για να τεθούν σε ισχύ οι αλλαγές." }, "excludedDomainsDescAlt": { - "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." + "message": "Το Bitwarden δε θα ρωτήσει για να αποθηκεύσετε τα στοιχεία σύνδεσης για αυτούς τους τομείς, για όλους τους συνδεδεμένους λογαριασμούς. Πρέπει να ανανεώσετε τη σελίδα για να τεθούν σε ισχύ οι αλλαγές." }, "excludedDomainsInvalidDomain": { "message": "Το $DOMAIN$ δεν είναι έγκυρος τομέας", @@ -2186,7 +2213,7 @@ "message": "Απαιτείται Επαλήθευση Email" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "Διεύθυνση ηλεκτρονικού ταχυδρομείου επιβεβαιώθηκε" }, "emailVerificationRequiredDesc": { "message": "Πρέπει να επαληθεύσετε το email σας για να χρησιμοποιήσετε αυτή τη δυνατότητα. Μπορείτε να επαληθεύσετε το email σας στο web vault." @@ -2213,19 +2240,19 @@ "message": "Επιλέξτε φάκελο..." }, "noFoldersFound": { - "message": "No folders found", + "message": "Δε βρέθηκαν φάκελοι", "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "Your organization permissions were updated, requiring you to set a master password.", + "message": "Τα δικαιώματα του οργανισμού σας ενημερώθηκαν, απαιτώντας από εσάς να ορίσετε έναν κύριο κωδικό πρόσβασης.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Your organization requires you to set a master password.", + "message": "Ο οργανισμός σας απαιτεί να ορίσετε έναν κύριο κωδικό πρόσβασης.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "verificationRequired": { - "message": "Verification required", + "message": "Απαιτείται επαλήθευση", "description": "Default title for the user verification dialog." }, "hours": { @@ -2277,10 +2304,10 @@ "message": "Το χρονικό όριο του vault σας υπερβαίνει τους περιορισμούς που έχει ορίσει ο οργανισμός σας." }, "vaultExportDisabled": { - "message": "Εξαγωγή vault Απενεργοποιημένη" + "message": "Μη διαθέσιμη εξαγωγή θησαυ/κίου" }, "personalVaultExportPolicyInEffect": { - "message": "Μία ή περισσότερες οργανωτικές πολιτικές σας αποτρέπει από την εξαγωγή του προσωπικού vault." + "message": "Μία ή περισσότερες πολιτικές οργανισμού σας αποτρέπει από την εξαγωγή του προσωπικού σας θησαυ/κίου." }, "copyCustomFieldNameInvalidElement": { "message": "Δεν είναι δυνατή η αναγνώριση ενός έγκυρου στοιχείου φόρμας. Δοκιμάστε να επιθεωρήσετε τον κώδικα HTML." @@ -2301,10 +2328,10 @@ "message": "Αποχώρηση από τον οργανισμό" }, "removeMasterPassword": { - "message": "Αφαίρεση Κύριου Κωδικού Πρόσβασης" + "message": "Αφαίρεση κύριου κωδικού πρόσβασης" }, "removedMasterPassword": { - "message": "Ο κύριος κωδικός αφαιρέθηκε." + "message": "Ο κύριος κωδικός πρόσβασης αφαιρέθηκε" }, "leaveOrganizationConfirmation": { "message": "Είστε βέβαιοι ότι θέλετε να φύγετε από αυτόν τον οργανισμό;" @@ -2319,10 +2346,10 @@ "message": "Έχει λήξει το χρονικό όριο. Παρακαλώ επιστρέψτε και προσπαθήστε να συνδεθείτε ξανά." }, "exportingPersonalVaultTitle": { - "message": "Εξαγωγή Προσωπικού Vault" + "message": "Εξαγωγή ατομικού θησαυ/κίου" }, "exportingIndividualVaultDescription": { - "message": "Μόνο τα μεμονωμένα αντικείμενα θησαυ/κιου που σχετίζονται με το $EMAIL$ θα εξαχθούν. Τα αντικείμενα θησαυ/κιου οργανισμού δε θα συμπεριληφθούν. Μόνο πληροφορίες αντικειμένων θησαυ/κιου θα εξαχθούν και δε θα περιλαμβάνουν συσχετιζόμενα συνημμένα.", + "message": "Μόνο τα ατομικά αντικείμενα θησαυ/κίου που σχετίζονται με το $EMAIL$ θα εξαχθούν. Τα αντικείμενα θησαυ/κίου του οργανισμού δε θα συμπεριληφθούν. Μόνο πληροφορίες αντικειμένων θησαυ/κίου θα εξαχθούν και δε θα περιλαμβάνουν συσχετιζόμενα συνημμένα.", "placeholders": { "email": { "content": "$1", @@ -2331,10 +2358,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "Εξαγωγή θησαυ/κίου οργανισμού" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "Μόνο το θησαυ/κιο του οργανισμού που σχετίζεται με το $ORGANIZATION$ θα εξαχθεί. Αντικείμενα σε ατομικά θησαυ/κια ή άλλους οργανισμούς δε θα συμπεριληφθούν.", "placeholders": { "organization": { "content": "$1", @@ -2346,13 +2373,13 @@ "message": "Σφάλμα" }, "regenerateUsername": { - "message": "Επαναδημιουργία Ονόματος Χρήστη" + "message": "Επαναδημιουργία ονόματος χρήστη" }, "generateUsername": { - "message": "Δημιουργία Όνομα Χρήστη" + "message": "Δημιουργία ονόματος χρήστη" }, "usernameType": { - "message": "Τύπος Ονόματος Χρήστη" + "message": "Τύπος ονόματος χρήστη" }, "plusAddressedEmail": { "message": "Συν Διεύθυνση Email", @@ -2362,7 +2389,7 @@ "message": "Χρησιμοποιήστε τις δυνατότητες δευτερεύουσας διεύθυνσης του παρόχου email σας." }, "catchallEmail": { - "message": "Catch-all Email" + "message": "Ηλεκτρονικό ταχυδρομείο για κάθε σκοπό" }, "catchallEmailDesc": { "message": "Χρησιμοποιήστε τα διαμορφωμένα εισερχόμενα catch-all του domain σας." @@ -2371,28 +2398,28 @@ "message": "Τυχαίο" }, "randomWord": { - "message": "Τυχαία Λέξη" + "message": "Τυχαία λέξη" }, "websiteName": { - "message": "Όνομα Ιστοσελίδας" + "message": "Όνομα ιστοσελίδας" }, "whatWouldYouLikeToGenerate": { "message": "Τι θα θέλατε να δημιουργήσετε?" }, "passwordType": { - "message": "Τύπος Κωδικού" + "message": "Τύπος κωδικού πρόσβασης" }, "service": { "message": "Υπηρεσία" }, "forwardedEmail": { - "message": "Προωθημένο Email Alias" + "message": "Προωθημένο ψευδώνυμο διεύθυνσης ηλεκτρονικού ταχυδρομείου" }, "forwardedEmailDesc": { "message": "Δημιουργήστε ένα alias email με μια εξωτερική υπηρεσία προώθησης." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ σφάλμα: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2406,11 +2433,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Δημιουργήθηκε από το Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Ιστοσελίδα: $WEBSITE$. Δημιουργήθηκε από το Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2420,7 +2447,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Μη έγκυρο $SERVICENAME$ διακριτικό API", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2430,7 +2457,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Μη έγκυρο $SERVICENAME$ API διακριτικό: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2444,7 +2471,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "Αδύνατη η απόκτηση του $SERVICENAME$ κρυφού ID λογαριασμού ηλεκτρονικού ταχυδρομείου.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2454,7 +2481,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Μη έγκυρος $SERVICENAME$ τομέας.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2464,7 +2491,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "Μη έγκυρο $SERVICENAME$ url.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2474,7 +2501,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Παρουσιάστηκε άγνωστο $SERVICENAME$ σφάλμα.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2484,7 +2511,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Άγνωστος διαβιβαστής: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2504,7 +2531,7 @@ "message": "Kλειδί API" }, "ssoKeyConnectorError": { - "message": "Σφάλμα Key Connector: βεβαιωθείτε ότι το Key Connector είναι διαθέσιμο και λειτουργεί σωστά." + "message": "Σφάλμα Key connector: βεβαιωθείτε ότι το Key connector είναι διαθέσιμο και λειτουργεί σωστά." }, "premiumSubcriptionRequired": { "message": "Απαιτείται συνδρομή Premium" @@ -2570,10 +2597,10 @@ "message": "Δεν είστε εσείς;" }, "newAroundHere": { - "message": "Νέος/α στα μέρη μας;" + "message": "Είστε νέος/α εδώ;" }, "rememberEmail": { - "message": "Απομνημόνευση email" + "message": "Απομνημόνευση διεύθυνσης ηλεκτρονικού ταχυδρομείου" }, "loginWithDevice": { "message": "Σύνδεση με τη χρήση συσκευής" @@ -2582,7 +2609,7 @@ "message": "Η σύνδεση με τη χρήση συσκευής πρέπει να έχει ρυθμιστεί στις ρυθμίσεις της εφαρμογής Bitwarden. Χρειάζεστε κάποια άλλη επιλογή;" }, "fingerprintPhraseHeader": { - "message": "Φράση δακτυλικών αποτυπωμάτων" + "message": "Φράση δακτυλικού αποτυπώματος" }, "fingerprintMatchInfo": { "message": "Βεβαιωθείτε ότι το vault σας είναι ξεκλειδωμένο και η Φράση δακτυλικών αποτυπωμάτων ταιριάζει στην άλλη συσκευή." @@ -2606,13 +2633,13 @@ "message": "Ο κωδικός έχει βρεθεί σε παραβίαση δεδομένων. Χρησιμοποιήστε έναν μοναδικό κωδικό για την προστασία του λογαριασμού σας. Είστε σίγουροι ότι θέλετε να χρησιμοποιήσετε έναν εκτεθειμένο κωδικό πρόσβασης;" }, "weakAndExposedMasterPassword": { - "message": "Αδύναμος και εκτεθειμένος Κύριος Κωδικός Πρόσβασης" + "message": "Αδύναμος και Εκτεθειμένος Κύριος Κωδικός Πρόσβασης" }, "weakAndBreachedMasterPasswordDesc": { "message": "Αδύναμος κωδικός που έχει εντοπιστεί σε παραβίαση δεδομένων. Χρησιμοποιήστε έναν ισχυρό και μοναδικό κωδικό για την προστασία του λογαριασμού σας. Είστε σίγουροι ότι θέλετε να χρησιμοποιήσετε αυτόν τον κωδικό;" }, "checkForBreaches": { - "message": "Ελέγξτε γνωστές παραβιάσεις δεδομένων για αυτόν τον κωδικό πρόσβασης" + "message": "Ελέγξτε γνωστές διαρροές δεδομένων για αυτόν τον κωδικό πρόσβασης" }, "important": { "message": "Σημαντικό:" @@ -2636,7 +2663,7 @@ "message": "Πώς να συμπληρώσετε αυτόματα" }, "autofillSelectInfoWithCommand": { - "message": "Select an item from this screen, use the shortcut $COMMAND$, or explore other options in settings.", + "message": "Επιλέξτε ένα αντικείμενο από αυτήν την οθόνη, χρησιμοποιήστε τη συντόμευση $COMMAND$, ή εξερευνήστε άλλες επιλογές στις ρυθμίσεις.", "placeholders": { "command": { "content": "$1", @@ -2645,7 +2672,7 @@ } }, "autofillSelectInfoWithoutCommand": { - "message": "Select an item from this screen, or explore other options in settings." + "message": "Επιλέξτε ένα αντικείμενο από αυτήν την οθόνη ή εξερευνήστε άλλες επιλογές στις ρυθμίσεις." }, "gotIt": { "message": "Το κατάλαβα" @@ -2705,25 +2732,25 @@ "message": "Απαιτείται αναγνωριστικό οργανισμού SSO." }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Δημιουργία λογαριασμού στο" }, "checkYourEmail": { - "message": "Check your email" + "message": "Ελέγξτε το ηλεκτρονικό σας ταχυδρομείο" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "Ακολουθήστε το σύνδεσμο στο μήνυμα ηλεκτρονικού ταχυδρομείου που στάλθηκε στο" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "και συνεχίστε στη δημιουργία του λογαριασμού σας." }, "noEmail": { - "message": "No email?" + "message": "Δεν υπάρχει μήνυμα ηλεκτρονικού ταχυδρομείου;" }, "goBack": { - "message": "Go back" + "message": "Επιστροφή" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "για να επεξεργαστείτε τη διεύθυνση ηλεκτρονικού ταχυδρομείου σας." }, "eu": { "message": "ΕΕ", @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Αξιόπιστη συσκευή" }, + "sendsNoItemsTitle": { + "message": "Κανένα ενεργό Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Χρήση Send για ασφαλή κοινοποίηση κρυπτογραφημένων πληροφοριών με οποιονδήποτε.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Απαιτείται εισαγωγή." }, @@ -2867,31 +2902,31 @@ "description": "Toggling an expand/collapse state." }, "filelessImport": { - "message": "Import your data to Bitwarden?", + "message": "Εισαγωγή των δεδομένων σας στο Bitwarden;", "description": "Default notification title for triggering a fileless import." }, "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", + "message": "Προστατέψτε τα δεδομένα LastPass και εισαγάγετε στο Bitwarden;", "description": "LastPass specific notification title for triggering a fileless import." }, "lpCancelFilelessImport": { - "message": "Save as unencrypted file", + "message": "Αποθήκευση ως μη κρυπτογραφημένο αρχείο", "description": "LastPass specific notification button text for cancelling a fileless import." }, "startFilelessImport": { - "message": "Import to Bitwarden", + "message": "Εισαγωγή στο Bitwarden", "description": "Notification button text for starting a fileless import." }, "importing": { - "message": "Importing...", + "message": "Εισαγωγή...", "description": "Notification message for when an import is in progress." }, "dataSuccessfullyImported": { - "message": "Data successfully imported!", + "message": "Επιτυχής εισαγωγή δεδομένων!", "description": "Notification message for when an import has completed successfully." }, "dataImportFailed": { - "message": "Error importing. Check console for details.", + "message": "Σφάλμα εισαγωγής. Ελέγξτε την κονσόλα για λεπτομέρειες.", "description": "Notification message for when an import has failed." }, "importNetworkError": { @@ -2914,13 +2949,13 @@ "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "Εναλλαγή πλευρικής πλοήγησης" }, "skipToContent": { - "message": "Skip to content" + "message": "Μετάβαση στο περιεχόμενο" }, "bitwardenOverlayButton": { - "message": "Bitwarden auto-fill menu button", + "message": "Πλήκτρο μενού αυτόματης συμπλήρωσης Bitwarden", "description": "Page title for the iframe containing the overlay button" }, "toggleBitwardenVaultOverlay": { @@ -3022,16 +3057,16 @@ "message": "Use master password" }, "usePin": { - "message": "Use PIN" + "message": "Χρήση PIN" }, "useBiometrics": { - "message": "Use biometrics" + "message": "Χρήση βιομετρικών" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "Εισάγετε τον κωδικό επαλήθευσης που έχει σταλεί στο ηλεκτρονικό ταχυδρομείο σας." }, "resendCode": { - "message": "Resend code" + "message": "Επαναποστολή κωδικού" }, "total": { "message": "Σύνολο" @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3058,7 +3096,7 @@ "message": "Popout extension" }, "launchDuo": { - "message": "Launch Duo" + "message": "Εκκίνηση Duo" }, "importFormatError": { "message": "Τα δεδομένα δεν έχουν διαμορφωθεί σωστά. Ελέγξτε το αρχείο εισαγωγής και δοκιμάστε ξανά." @@ -3085,7 +3123,7 @@ "message": "Επιλέξτε μια συλλογή" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "Επιλέξτε αυτή την επιλογή εάν θέλετε τα περιεχόμενα του εισαγόμενου αρχείου να μετακινηθούν στο $DESTINATION$", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -3132,28 +3170,28 @@ "message": "Επιβεβαίωση κωδικού πρόσβασης αρχείου" }, "exportSuccess": { - "message": "Vault data exported" + "message": "Εξήχθησαν τα δεδομένα θησαυ/κίου" }, "typePasskey": { - "message": "Passkey" + "message": "Κλειδί πρόσβασης" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "Το κλειδί πρόσβασης δε θα αντιγραφεί" }, "passkeyNotCopiedAlert": { - "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" + "message": "Το κλειδί πρόσβασης δε θα αντιγραφεί στο κλωνοποιημένο αντικείμενο. Θέλετε να συνεχίσετε την κλωνοποίηση αυτού του αντικειμένου;" }, "passkeyFeatureIsNotImplementedForAccountsWithoutMasterPassword": { "message": "Απαιτείται επαλήθευση από τον ιστότοπο εκκίνησης. Αυτή η λειτουργία δεν έχει ακόμα υλοποιηθεί για λογαριασμούς χωρίς τον κύριο κωδικό πρόσβασης." }, "logInWithPasskey": { - "message": "Log in with passkey?" + "message": "Σύνδεση με κλειδί πρόσβασης;" }, "passkeyAlreadyExists": { - "message": "A passkey already exists for this application." + "message": "Υπάρχει ήδη ένα κλειδί πρόσβασης για αυτήν την εφαρμογή." }, "noPasskeysFoundForThisApplication": { - "message": "No passkeys found for this application." + "message": "Δε βρέθηκαν κλειδιά πρόσβασης για αυτήν την εφαρμογή." }, "noMatchingPasskeyLogin": { "message": "Δεν έχετε στοιχεία σύνδεσης που να συνδυάζονται με αυτόν τον ιστότοπο." @@ -3162,28 +3200,28 @@ "message": "Επιβεβαίωση" }, "savePasskey": { - "message": "Save passkey" + "message": "Αποθήκευση κλειδιού πρόσβασης" }, "savePasskeyNewLogin": { - "message": "Save passkey as new login" + "message": "Αποθήκευση κλειδιού πρόσβασης ως νέα σύνδεση" }, "choosePasskey": { - "message": "Choose a login to save this passkey to" + "message": "Επιλέξτε μια σύνδεση στην οποία θα αποθηκεύσετε αυτό το κλειδί πρόσβασης" }, "passkeyItem": { - "message": "Passkey Item" + "message": "Αντικείμενο κλειδιού πρόσβασης" }, "overwritePasskey": { - "message": "Overwrite passkey?" + "message": "Αντικατάσταση κλειδιού πρόσβασης;" }, "overwritePasskeyAlert": { - "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" + "message": "Αυτό το αντικείμενο περιέχει ήδη ένα κλειδί πρόσβασης. Είστε σίγουροι ότι θέλετε να αντικαταστήσετε το τρέχον κλειδί πρόσβασης;" }, "featureNotSupported": { "message": "Η λειτουργία δεν υποστηρίζεται ακόμη" }, "yourPasskeyIsLocked": { - "message": "Authentication required to use passkey. Verify your identity to continue." + "message": "Απαιτείται αυθεντικοποίηση για χρήση κλειδιού πρόσβασης. Επαληθεύστε την ταυτότητά σας για να συνεχίσετε." }, "multifactorAuthenticationCancelled": { "message": "Ο πολυμερής έλεγχος ταυτότητας ακυρώθηκε" @@ -3195,13 +3233,13 @@ "message": "Λάθος όνομα χρήστη ή κωδικού πρόσβασης" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "Εσφαλμένος κωδικός πρόσβασης" }, "incorrectCode": { - "message": "Incorrect code" + "message": "Εσφαλμένος κωδικός" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "Εσφαλμένο PIN" }, "multifactorAuthenticationFailed": { "message": "Ο πολυμερής έλεγχος ταυτότητας απέτυχε" @@ -3225,7 +3263,7 @@ "message": "Εγκρίνετε το αίτημα σύνδεσης στην εφαρμογή ελέγχου ταυτότητας ή εισαγάγετε έναν κωδικό πρόσβασης μιας χρήσης." }, "passcode": { - "message": "Passcode" + "message": "Κωδικός" }, "lastPassMasterPassword": { "message": "Κύριος κωδικός πρόσβασης LastPass" @@ -3256,49 +3294,49 @@ "message": "Συλλογή" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "Εισάγετε το YubiKey που σχετίζεται με τον λογαριασμό LastPass στη θύρα USB του υπολογιστή σας, και στη συνέχεια αγγίξτε το κουμπί του." }, "switchAccount": { - "message": "Switch account" + "message": "Αλλαγή λογαριασμού" }, "switchAccounts": { - "message": "Switch accounts" + "message": "Αλλαγή λογαριασμών" }, "switchToAccount": { - "message": "Switch to account" + "message": "Αλλαγή σε λογαριασμό" }, "activeAccount": { - "message": "Active account" + "message": "Ενεργός λογαριασμός" }, "availableAccounts": { - "message": "Available accounts" + "message": "Διαθέσιμοι λογαριασμοί" }, "accountLimitReached": { - "message": "Account limit reached. Log out of an account to add another." + "message": "Συμπληρώθηκε το όριο λογαριασμού. Αποσυνδεθείτε από έναν λογαριασμό για να προσθέσετε έναν άλλο." }, "active": { - "message": "active" + "message": "ενεργό" }, "locked": { - "message": "locked" + "message": "κλειδωμένο" }, "unlocked": { - "message": "unlocked" + "message": "ξεκλείδωτο" }, "server": { - "message": "server" + "message": "διακομιστής" }, "hostedAt": { "message": "hosted at" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "Χρήση της συσκευής ή του φυσικού κλειδιού σας" }, "justOnce": { - "message": "Just once" + "message": "Μόνο μία φορά" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "Πάντα για αυτήν την ιστοσελίδα" }, "domainAddedToExcludedDomains": { "message": "$DOMAIN$ added to excluded domains.", @@ -3310,11 +3348,11 @@ } }, "commonImportFormats": { - "message": "Common formats", + "message": "Κοινοί τύποι", "description": "Label indicating the most common import formats" }, "overrideDefaultBrowserAutofillTitle": { - "message": "Make Bitwarden your default password manager?", + "message": "Να γίνει το Bitwarden ο προεπιλεγμένος διαχειριστής κωδικών πρόσβασης σας;", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { @@ -3322,56 +3360,56 @@ "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "Make Bitwarden your default password manager", + "message": "Να γίνει το Bitwarden ο προεπιλεγμένος διαχειριστής κωδικών πρόσβασης σας", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { - "message": "Unable to set Bitwarden as the default password manager", + "message": "Αδυναμία ορισμού του Bitwarden ως προεπιλεγμένου διαχειριστή κωδικών πρόσβασης", "description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "privacyPermissionAdditionNotGrantedDescription": { - "message": "You must grant browser privacy permissions to Bitwarden to set it as the default password manager.", + "message": "Πρέπει να παραχωρήσετε δικαιώματα απορρήτου περιηγητή στο Bitwarden για να το ορίσετε ως προεπιλεγμένο διαχειριστή κωδικών πρόσβασης.", "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { - "message": "Make default", + "message": "Ορισμός ως προεπιλογή", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "Τα στοιχεία αποθηκεύτηκαν επιτυχώς!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "Τα στοιχεία ενημερώθηκαν επιτυχώς!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { - "message": "Error saving credentials. Check console for details.", + "message": "Σφάλμα αποθήκευσης στοιχείων. Ελέγξτε την κονσόλα για λεπτομέρειες.", "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "Επιτυχία" }, "removePasskey": { - "message": "Remove passkey" + "message": "Αφαίρεση κλειδιού πρόσβασης" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "Το κλειδί πρόσβασης αφαιρέθηκε" }, "autofillSuggestions": { - "message": "Auto-fill suggestions" + "message": "Προτάσεις αυτόματης συμπλήρωσης" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to auto-fill" + "message": "Αποθηκεύστε ένα αντικείμενο σύνδεσης για την αυτόματη συμπλήρωση αυτού του ιστοτόπου" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "Το θησαυ/κιό σας είναι άδειο" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "Κανένα αντικείμενο δεν ταιριάζει με την αναζήτησή σας" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "Καθαρισμός φίλτρων ή δοκιμή άλλου όρου αναζήτησης" }, "copyInfoTitle": { "message": "Copy info - $ITEMNAME$", @@ -3605,11 +3643,17 @@ } } }, + "loginCredentials": { + "message": "Στοιχεία σύνδεσης" + }, + "authenticatorKey": { + "message": "Κλειδί αυθεντικοποίησης" + }, "cardDetails": { - "message": "Card details" + "message": "Στοιχεία κάρτας" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$ λεπτομέρειες", "placeholders": { "brand": { "content": "$1", @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Προσθήκη λογαριασμού" + }, + "loading": { + "message": "Φόρτωση" + }, + "assign": { + "message": "Ανάθεση" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Μόνο τα μέλη του οργανισμού με πρόσβαση σε αυτές τις συλλογές θα είναι σε θέση να δουν τα αντικείμενα." + }, + "bulkCollectionAssignmentWarning": { + "message": "Έχετε επιλέξει $TOTAL_COUNT$ αντικείμενα. Δεν μπορείτε να ενημερώσετε τα $READONLY_COUNT$ αντικείμενα επειδή δεν έχετε δικαιώματα επεξεργασίας.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Προσθήκη πεδίου" + }, + "add": { + "message": "Προσθήκη" + }, + "fieldType": { + "message": "Τύπος πεδίου" + }, + "fieldLabel": { + "message": "Ετικέτα πεδίου" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Επεξεργασία πεδίου" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Επιλέξτε συλλογές για ανάθεση" + }, + "personalItemsTransferWarning": { + "message": "Το $PERSONAL_ITEMS_COUNT$ θα μεταφερθεί μόνιμα στον επιλεγμένο οργανισμό. Δε θα κατέχετε πλέον αυτά τα αντικείμενα.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "Το $PERSONAL_ITEMS_COUNT$ θα μεταφερθεί μόνιμα στο $ORG$. Δε θα κατέχετε πλέον αυτά τα αντικείμενα.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Επιτυχής ανάθεση συλλογών" + }, + "nothingSelected": { + "message": "Δεν έχετε επιλέξει τίποτα." + }, + "movedItemsToOrg": { + "message": "Τα επιλεγμένα αντικείμενα μετακινήθηκαν στο $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 462b6eedbef..4bd656a282c 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -108,7 +108,7 @@ "message": "Copy security code" }, "autoFill": { - "message": "Auto-fill" + "message": "Autofill" }, "autoFillLogin": { "message": "Auto-fill login" @@ -660,6 +660,21 @@ "loginExpired": { "message": "Your login session has expired." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -721,6 +736,10 @@ "newUri": { "message": "New URI" }, + "addDomain": { + "message": "Add domain", + "description": "'Domain' here refers to an internet domain name (e.g. 'bitwarden.com') and the message in whole described the act of putting a domain value into the context." + }, "addedItem": { "message": "Item added" }, @@ -761,18 +780,27 @@ "enableAddLoginNotification": { "message": "Ask to add login" }, + "vaultSaveOptionsTitle": { + "message": "Save to vault options" + }, "addLoginNotificationDesc": { "message": "Ask to add an item if one isn't found in your vault." }, "addLoginNotificationDescAlt": { "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, + "showCardsInVaultView": { + "message": "Show cards as Autofill suggestions on Vault view" + }, "showCardsCurrentTab": { "message": "Show cards on Tab page" }, "showCardsCurrentTabDesc": { "message": "List card items on the Tab page for easy auto-fill." }, + "showIdentitiesInVaultView": { + "message": "Show identifies as Autofill suggestions on Vault view" + }, "showIdentitiesCurrentTab": { "message": "Show identities on Tab page" }, @@ -1205,11 +1233,20 @@ "message": "Show auto-fill menu on form fields", "description": "Represents the message for allowing the user to enable the auto-fill overlay" }, - "showAutoFillMenuOnFormFieldsDescAlt": { + "autofillSuggestionsSectionTitle": { + "message": "Autofill suggestions" + }, + "showInlineMenuLabel": { + "message": "Show autofill suggestions on form fields" + }, + "showInlineMenuOnIconSelectionLabel": { + "message": "Display suggestions when icon is selected" + }, + "showInlineMenuOnFormFieldsDescAlt": { "message": "Applies to all logged in accounts." }, "turnOffBrowserBuiltInPasswordManagerSettings": { - "message": "Turn off your browser’s built in password manager settings to avoid conflicts." + "message": "Turn off your browser's built in password manager settings to avoid conflicts." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { "message": "Edit browser settings." @@ -1226,23 +1263,43 @@ "message": "When auto-fill icon is selected", "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, + "enableAutoFillOnPageLoadSectionTitle": { + "message": "Autofill on page load" + }, "enableAutoFillOnPageLoad": { - "message": "Auto-fill on page load" + "message": "Autofill on page load" }, "enableAutoFillOnPageLoadDesc": { - "message": "If a login form is detected, auto-fill when the web page loads." + "message": "If a login form is detected, autofill when the web page loads." + }, + "autofillOnPageLoadWarning": { + "message": "$OPENTAG$Warning:$CLOSETAG$ Compromised or untrusted websites can exploit autofill on page load.", + "placeholders": { + "openTag": { + "content": "$1", + "example": "" + }, + "closeTag": { + "content": "$2", + "example": "" + } + + } }, "experimentalFeature": { - "message": "Compromised or untrusted websites can exploit auto-fill on page load." + "message": "Compromised or untrusted websites can exploit autofill on page load." + }, + "learnMoreAboutAutofillOnPageLoadLinkText": { + "message": "Learn more about risks" }, "learnMoreAboutAutofill": { - "message": "Learn more about auto-fill" + "message": "Learn more about autofill" }, "defaultAutoFillOnPageLoad": { "message": "Default autofill setting for login items" }, "defaultAutoFillOnPageLoadDesc": { - "message": "You can turn off auto-fill on page load for individual login items from the item's Edit view." + "message": "You can turn off autofill on page load for individual login items from the item's Edit view." }, "itemAutoFillOnPageLoad": { "message": "Auto-fill on page load (if set up in Options)" @@ -1251,10 +1308,10 @@ "message": "Use default setting" }, "autoFillOnPageLoadYes": { - "message": "Auto-fill on page load" + "message": "Autofill on page load" }, "autoFillOnPageLoadNo": { - "message": "Do not auto-fill on page load" + "message": "Do not autofill on page load" }, "commandOpenPopup": { "message": "Open vault popup" @@ -1295,6 +1352,9 @@ "cfTypeBoolean": { "message": "Boolean" }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "cfTypeLinked": { "message": "Linked", "description": "This describes a field that is 'linked' (tied) to another field." @@ -1832,8 +1892,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -1952,6 +2012,10 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "domainsTitle": { + "message": "Domains", + "description": "A category title describing the concept of web domains" + }, "excludedDomains": { "message": "Excluded domains" }, @@ -1961,15 +2025,27 @@ "excludedDomainsDescAlt": { "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." }, + "websiteItemLabel": { + "message": "Website $number$ (URI)", + "placeholders": { + "number": { + "content": "$1", + "example": "3" + } + } + }, "excludedDomainsInvalidDomain": { "message": "$DOMAIN$ is not a valid domain", "placeholders": { "domain": { "content": "$1", - "example": "google.com" + "example": "duckduckgo.com" } } }, + "excludedDomainsSavedSuccess": { + "message": "Excluded domain changes saved" + }, "send": { "message": "Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2665,14 +2741,20 @@ "autofillSettings": { "message": "Auto-fill settings" }, + "autofillKeyboardShortcutSectionTitle": { + "message": "Autofill shortcut" + }, + "autofillKeyboardShortcutUpdateLabel": { + "message": "Change shortcut" + }, "autofillShortcut": { - "message": "Auto-fill keyboard shortcut" + "message": "Autofill keyboard shortcut" }, "autofillShortcutNotSet": { - "message": "The auto-fill shortcut is not set. Change this in the browser's settings." + "message": "The autofill shortcut is not set. Change this in the browser's settings." }, "autofillShortcutText": { - "message": "The auto-fill shortcut is: $COMMAND$. Change this in the browser's settings.", + "message": "The autofill shortcut is: $COMMAND$. Change this in the browser's settings.", "placeholders": { "command": { "content": "$1", @@ -3095,8 +3177,8 @@ "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, - "importDestination": { - "message": "Import destination" + "destination": { + "message": "Destination" }, "learnAboutImportOptions": { "message": "Learn about your import options" @@ -3328,7 +3410,7 @@ "placeholders": { "domain": { "content": "$1", - "example": "google.com" + "example": "duckduckgo.com" } } }, @@ -3336,12 +3418,36 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, + "confirmContinueToBrowserSettingsTitle": { + "message": "Continue to browser settings?", + "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" + }, + "confirmContinueToHelpCenter": { + "message": "Continue to Help Center?", + "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" + }, + "confirmContinueToHelpCenterPasswordManagementContent": { + "message": "Change your browser's autofill and password management settings.", + "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser password management settings" + }, + "confirmContinueToHelpCenterKeyboardShortcutsContent": { + "message": "You can view and set extension shortcuts in your browser's settings.", + "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser keyboard shortcut settings" + }, + "confirmContinueToBrowserPasswordManagementSettingsContent": { + "message": "Change your browser's autofill and password management settings.", + "description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page" + }, + "confirmContinueToBrowserKeyboardShortcutSettingsContent": { + "message": "You can view and set extension shortcuts in your browser's settings.", + "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" + }, "overrideDefaultBrowserAutofillTitle": { "message": "Make Bitwarden your default password manager?", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "Ignoring this option may cause conflicts between the Bitwarden auto-fill menu and your browser's.", + "message": "Ignoring this option may cause conflicts between Bitwarden autofill suggestions and your browser's.", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { @@ -3459,8 +3565,8 @@ "noValuesToCopy": { "message": "No values to copy" }, - "assignCollections": { - "message": "Assign collections" + "assignToCollections": { + "message": "Assign to collections" }, "copyEmail": { "message": "Copy email" @@ -3652,10 +3758,16 @@ "loading": { "message": "Loading" }, + "data": { + "message": "Data" + }, "assign": { "message": "Assign" }, - "bulkCollectionAssignmentDialogDescription": { + "bulkCollectionAssignmentDialogDescriptionSingular": { + "message": "Only organization members with access to these collections will be able to see the item." + }, + "bulkCollectionAssignmentDialogDescriptionPlural": { "message": "Only organization members with access to these collections will be able to see the items." }, "bulkCollectionAssignmentWarning": { @@ -3756,21 +3868,33 @@ "selectCollectionsToAssign": { "message": "Select collections to assign" }, - "personalItemsTransferWarning": { - "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "personalItemTransferWarningSingular": { + "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + }, + "personalItemsTransferWarningPlural": { + "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", "placeholders": { "personal_items_count": { "content": "$1", - "example": "2 items" + "example": "2" } } }, - "personalItemsWithOrgTransferWarning": { - "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "personalItemWithOrgTransferWarningSingular": { + "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "placeholders": { + "org": { + "content": "$1", + "example": "Organization name" + } + } + }, + "personalItemsWithOrgTransferWarningPlural": { + "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", "placeholders": { "personal_items_count": { "content": "$1", - "example": "2 items" + "example": "2" }, "org": { "content": "$2", @@ -3793,6 +3917,24 @@ } } }, + "itemsMovedToOrg": { + "message": "Items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "itemMovedToOrg": { + "message": "Item moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, "reorderFieldDown":{ "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", "placeholders": { @@ -3809,5 +3951,8 @@ "example": "3" } } + }, + "itemLocation": { + "message": "Item Location" } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 886c6082673..96af6b92ed2 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Your login session has expired." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organisation members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a reminder email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organisation. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 6db451bdb34..e79a8afece4 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Your login session has expired." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a reminder email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index cad044f9921..dfddc46ec87 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -557,16 +557,16 @@ "message": "Seguridad" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "Confirmar contraseña maestra" }, "masterPassword": { - "message": "Master password" + "message": "Contraseña maestra" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "¡Tu contraseña maestra no se puede recuperar si la olvidas!" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Pista de la contraseña maestra" }, "errorOccurred": { "message": "Ha ocurrido un error" @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Código de verificación requerido." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Código de verificación no válido" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Escanee el código QR del autenticador desde la página web actual" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copiar clave de autenticador (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Tu sesión ha expirado." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "¿Estás seguro de querer cerrar la sesión?" }, @@ -1484,7 +1511,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "Ver $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Su nueva contraseña maestra no cumple con los requisitos de la política." }, - "receiveMarketingEmails": { - "message": "Obtén correos electrónicos de Bitwarden para anuncios, consejos y oportunidades de investigación." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Cancelar suscripción" @@ -2186,7 +2213,7 @@ "message": "Verificación de correo electrónico requerida" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "Correo electrónico verificado" }, "emailVerificationRequiredDesc": { "message": "Debes verificar tu correo electrónico para usar esta función. Puedes verificar tu correo electrónico en la caja fuerte web." @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Dispositivo de confianza" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Entrada requerida." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error al conectarse con el servicio Duo. Utiliza un método de inicio de sesión en dos pasos diferente o ponte en contacto con Duo para obtener ayuda." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Abra Duo y siga los pasos para terminar de iniciar sesión." }, @@ -3503,13 +3541,13 @@ "message": "Elementos sin carpeta" }, "itemDetails": { - "message": "Item details" + "message": "Detalles del elemento" }, "itemName": { - "message": "Item name" + "message": "Nombre del elemento" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "No puedes eliminar colecciones con permisos de solo visualización: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -3521,32 +3559,32 @@ "message": "La organización está desactivada" }, "owner": { - "message": "Owner" + "message": "Propietario" }, "selfOwnershipLabel": { - "message": "You", + "message": "Tú", "description": "Used as a label to indicate that the user is the owner of an item." }, "contactYourOrgAdmin": { "message": "No se puede acceder a los elementos de las organizaciones desactivadas. Ponte en contacto con el propietario de tu organización para obtener ayuda." }, "additionalInformation": { - "message": "Additional information" + "message": "Información adicional" }, "itemHistory": { - "message": "Item history" + "message": "Historial del elemento" }, "lastEdited": { - "message": "Last edited" + "message": "Última edición" }, "ownerYou": { - "message": "Owner: You" + "message": "Propietario: Tú" }, "linked": { - "message": "Linked" + "message": "Vinculado" }, "copySuccessful": { - "message": "Copy Successful" + "message": "Copiado exitosamente" }, "upload": { "message": "Subir" @@ -3588,16 +3626,16 @@ "message": "Filtros" }, "personalDetails": { - "message": "Personal details" + "message": "Datos personales" }, "identification": { - "message": "Identification" + "message": "Identificación" }, "contactInfo": { - "message": "Contact info" + "message": "Información de contacto" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "Descargar - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -3605,11 +3643,17 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { - "message": "Card details" + "message": "Datos de la tarjeta" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "Detalles de $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Añadir cuenta" + }, + "loading": { + "message": "Cargando" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index d6b7120294f..b22ffec37d8 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Nõutav on kinnituskood." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Vale kinnituskood" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Sessioon on aegunud." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Oled kindel, et soovid välja logida?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Uus ülemparool ei vasta eeskirjades väljatoodud tingimustele." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Seade on usaldusväärne" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Sisestus on nõutav." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 414d1764039..96c807caac7 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Egiaztatze-kodea behar da." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Egiaztatze-kodea ez da baliozkoa" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Saioa amaitu da." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Ziur zaude saioa itxi nahi duzula?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Zure pasahitz nagusi berriak ez ditu baldintzak betetzen." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index ffc4370f20d..7c232bcd8bc 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "کد تأیید مورد نیاز است." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "نشست ورود شما منقضی شده است." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "آیا مطمئنید که می‌خواهید خارج شوید؟" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "کلمه عبور اصلی جدید شما از شرایط سیاست پیروی نمی‌کند." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "لغو اشتراک" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "دستگاه مورد اعتماد است" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "ورودی ضروری است." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 4758613a2ea..a4181253102 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Todennuskoodi vaaditaan." }, + "webauthnCancelOrTimeout": { + "message": "Todennus peruutettiin tai siinä kesti liian kauan. Yritä uudelleen." + }, "invalidVerificationCode": { "message": "Virheellinen todennuskoodi" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Skannaa todennusavaimen QR-koodi nykyiseltä verkkosivulta" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Kopioi todennusavain (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Kirjautumisistuntosi on erääntynyt." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Haluatko varmasti kirjautua ulos?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Uusi pääsalasanasi ei täytä käytännön määrittämiä vaatimuksia." }, - "receiveMarketingEmails": { - "message": "Vastaanota Bitwardenilta uutiskirjeitä julkaisuista, tukiresursseista ja tutkimusmahdollisuuksista." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Lopeta tilaus" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Laitteeseen luotettu" }, + "sendsNoItemsTitle": { + "message": "Aktiivisia Sendejä ei ole", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Sendillä voit jakaa salattuja tietoja turvallisesti kenelle tahansa.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Syöte vaaditaan." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Virhe yhdistettäessä Duo-palveluun. Käytä toista kaksivaiheista kirjautumismenetelmää tai ota yhteyttä Duoon saadaksesi apua." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Avaa Duo ja viimeistele kirjautuminen seuraamalla ohjeita." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Kortin tiedot" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Lisää tili" + }, + "loading": { + "message": "Ladataan" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Lisää kenttä" + }, + "add": { + "message": "Lisää" + }, + "fieldType": { + "message": "Kentän tyyppi" + }, + "fieldLabel": { + "message": "Kentän otsikko" + }, + "textHelpText": { + "message": "Käytä tekstikenttiä tiedoille, kuten turvakysymyksille" + }, + "hiddenHelpText": { + "message": "Käytä piilotettuja kenttiä arkaluonteisille tiedoille, kuten salasanoille" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Käytä linkitettyä kenttää, kun sinulla on automaattitäyttämiseen liittyviä ongelmia tietyllä verkkosivustolla." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Muokkaa kenttää" + }, + "editFieldLabel": { + "message": "Muokkaa $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Poista $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ lisättiin", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ siirrettiin ylös, sijainti: $INDEX$ / $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ siirrettiin alas, sijainti: $INDEX$ / $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 5efa5ca5cf3..3009e136842 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Kinakailangan ang verification code." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Maling verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Nag-expire na ang iyong session sa login." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Sigurado ka bang gusto mong mag-log out?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Hindi matugunan ng iyong bagong pangunahing password ang mga kinakailangan ng patakaran." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index d0f6aa710cb..24e04825f74 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Le code de vérification est requis." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Code de vérification invalide" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scanner le QR code de l'authentificateur à partir de la page web actuelle" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copier la clé Authenticator (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Votre session a expiré." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Êtes-vous sûr de vouloir vous déconnecter ?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Votre nouveau mot de passe principal ne répond pas aux exigences de politique de sécurité." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Se désabonner" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Appareil de confiance" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Saisie requise." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Lancez DUO et suivez les étapes pour terminer la connexion." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index c9b15d4ea0a..13f59f958e2 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "É preciso código de verificación." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Código de verificación non válido" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Escanea o código QR autenticador da páxina web actual" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copiar clave de autenticación (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "A túa sesión caducou." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 31d20be247e..d85872bbee5 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "נדרש קוד אימות." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "קוד אימות שגוי" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "תוקף החיבור שלך הסתיים." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "האם אתה בטוח שברצונך להתנתק?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "הסיסמה הראשית החדשה השלך לא עומדת בדרישות המדיניות." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 4b23d203704..0e7fd3bd9ae 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "सत्यापन टोकन आवश्यक है" }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "सत्यापन कोड अवैध है" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "अपने लॉगिन सत्र समाप्त हो गया है।" }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "क्या आप वाकई लॉग आउट करना चाहते हैं?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "आपका नया मास्टर पासवर्ड पॉलिसी आवश्यकताओं को पूरा नहीं करता है।" }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 266a7b2d323..74853546ee0 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Potvrdni kôd je obavezan." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Nevažeći kôd za provjeru" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Skenirajte QR kod autentifikatora s trenutne web stranice" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Kopiraj ključ autentifikatora (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Sesija je istekla." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Sigurno se želiš odjaviti?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Tvoja nova glavna lozinka ne ispunjava zahtjeve." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Uređaj pouzdan" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Potreban je unos." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 598f122b4f8..dd1627f57a8 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Ellenőrző kód szükséges." }, + "webauthnCancelOrTimeout": { + "message": "A hitelesítés megszakításra került vagy túl sokáig tartott. Próbáljuk újra." + }, "invalidVerificationCode": { "message": "Érvénytelen ellenőrző kód" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Hitelesítő QR kód szkennelése az aktuális weboldalról" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Hitelesítő kód másolása (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Bejelentkezési munkamenete lejárt." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Biztos benne, hogy ki szeretnél jelentkezni?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Az új mesterjelszó nem felel meg a szabály követelményeknek." }, - "receiveMarketingEmails": { - "message": "Emaileket kaphatunk a Bitwardentől bejelentésekről, tanácsokról és kutatási lehetőségekről." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Leiratkozás" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Az eszköz megbízható." }, + "sendsNoItemsTitle": { + "message": "Nincsenek natív Send elemek.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "A Send használatával biztonságosan megoszthatjuk a titkosított információkat bárkivel.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Az adatbevitel kötelező." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Indítsuk el a DUO-t és kövessük a lépéseket a bejelentkezés befejezéséhez." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Kártyaadatok" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Fiók hozzáadása" + }, + "loading": { + "message": "A betöltés folyamatban van." + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Mező hozzáadása" + }, + "add": { + "message": "Hozzáadás" + }, + "fieldType": { + "message": "Mezőtípus" + }, + "fieldLabel": { + "message": "Mezőfelirat" + }, + "textHelpText": { + "message": "Szövegmezők használata olyan adatokhoz mint a biztonsági kérdések" + }, + "hiddenHelpText": { + "message": "Rejtett mezők használata olyan érzékeny adatokhoz mint a jelszó" + }, + "checkBoxHelpText": { + "message": "Jelölődobozok használata, ha automatikusan ki szeretnénk tölteni olyan űrlap jelölődobozt mint az email cím megjegyzése" + }, + "linkedHelpText": { + "message": "Csatolt mező használata, ha egy adott webhely automatikus kitöltésével kapcsolatos problémákat tapasztalunk." + }, + "linkedLabelHelpText": { + "message": "Adjuk meg a mező html azonosítóját, nevét, aria címkéjét vagy helyőrét." + }, + "editField": { + "message": "Mező szerkesztése" + }, + "editFieldLabel": { + "message": "$LABEL$ szerkesztése", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "$LABEL$ törlése", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ hozzáadásra került.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "$LABEL$ átrendezése. A nyílbillentyűkkel mozgassuk az elemet felfelé vagy lefelé.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ feljebb került, $INDEX$/$LENGTH$ pozícióba", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ lejjebb került, $INDEX$/$LENGTH$ pozícióba", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 8625ce67000..1cd7311e6cf 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Kode verifikasi diperlukan." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Kode verifikasi tidak valid" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Pindai kode QR autentikator dari laman ini" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Salin kunci Autentikator (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Sesi masuk Anda telah berakhir." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Anda yakin ingin keluar?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Kata sandi utama Anda yang baru tidak memenuhi persyaratan kebijakan." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index b4f3e1d240d..760eeee0544 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Il codice di verifica è obbligatorio." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Codice di verifica non valido" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scansiona il codice QR dell'autenticatore da questa pagina web" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copia la chiave di autenticazione (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "La tua sessione è scaduta." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Sei sicuro di volerti disconnettere?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "La tua nuova password principale non soddisfa i requisiti di sicurezza." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Dispositivo fidato" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input obbligatorio." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Avvia DUO e segui i passaggi per finire di accedere." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index ad45122d57b..9e5654f62c1 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "認証コードは必須項目です。" }, + "webauthnCancelOrTimeout": { + "message": "認証がキャンセルされたか、時間がかかりすぎました。もう一度やり直してください。" + }, "invalidVerificationCode": { "message": "認証コードが間違っています" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "現在のウェブページから認証 QR コードをスキャンする" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "認証キーのコピー (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "ログインセッションの有効期限が切れています。" }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "ログアウトしてもよろしいですか?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "新しいマスターパスワードは最低要件を満たしていません。" }, - "receiveMarketingEmails": { - "message": "Bitwarden からのお知らせ、アドバイス、アンケート調査等のメールを受信します。" + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "配信停止" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "信頼されたデバイス" }, + "sendsNoItemsTitle": { + "message": "アクティブな Send なし", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Send を使用すると暗号化された情報を誰とでも安全に共有できます。", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "入力が必要です。" }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Duo サービスへの接続中にエラーが発生しました。異なる二段階ログイン方法を使用するか、Duo に連絡してください。" + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "ログインを完了するには DUO を起動し手順に従ってください。" }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "カード情報" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "アカウントを追加" + }, + "loading": { + "message": "読み込み中" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "フィールドを追加" + }, + "add": { + "message": "追加" + }, + "fieldType": { + "message": "フィールドタイプ" + }, + "fieldLabel": { + "message": "フィールドラベル" + }, + "textHelpText": { + "message": "セキュリティに関する質問などのデータにはテキストフィールドを使用します" + }, + "hiddenHelpText": { + "message": "パスワードのような機密データには非表示フィールドを使用します" + }, + "checkBoxHelpText": { + "message": "メールアドレスの記憶などのフォームのチェックボックスを自動入力する場合はチェックボックスを使用します" + }, + "linkedHelpText": { + "message": "特定のウェブサイトで自動入力の問題が発生している場合は、リンクされたフィールドを使用します" + }, + "linkedLabelHelpText": { + "message": "フィールドの HTML ID、名前、aria-label、またはプレースホルダを入力します" + }, + "editField": { + "message": "フィールドを編集" + }, + "editFieldLabel": { + "message": "$LABEL$ を編集", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "$LABEL$ を削除", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ を追加しました", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "$LABEL$ の順序を変更します。矢印キーを押すとアイテムを上下に移動します。", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ を上に移動しました。$INDEX$ / $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ を下に移動しました。$INDEX$ / $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index df8dc0cce3e..2bda6bee217 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "ერთჯერადი კოდი აუცილებელია." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Your login session has expired." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index fa76cf7060a..f87a68a30dc 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Your login session has expired." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index fc2b2711c9f..da429e197eb 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "ಪರಿಶೀಲನೆ ಕೋಡ್ ಅಗತ್ಯವಿದೆ." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "ನಿಮ್ಮ ಲಾಗಿನ್ ಸೆಷನ್ ಅವಧಿ ಮೀರಿದೆ." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "ಲಾಗ್ ಔಟ್ ಮಾಡಲು ನೀವು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "ನಿಮ್ಮ ಹೊಸ ಮಾಸ್ಟರ್ ಪಾಸ್‌ವರ್ಡ್ ನೀತಿಯ ಅವಶ್ಯಕತೆಗಳನ್ನು ಪೂರೈಸುವುದಿಲ್ಲ." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 9762761b366..e9112a69672 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "인증 코드는 반드시 입력해야 합니다." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "유효하지 않은 확인 코드" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "현재 웹페이지에서 QR 코드 스캔하기" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "인증서 키 (TOTP) 복사" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "로그인 세션이 만료되었습니다." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "정말 로그아웃하시겠습니까?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "새 마스터 비밀번호가 정책 요구 사항을 따르지 않습니다." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index f09e9c21caa..1f17f6f888c 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Būtinas patvirtinimo kodas." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Neteisingas patvirtinimo kodas" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Nuskaitykite autentifikatoriaus QR kodą iš dabartinio tinklalapio" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Kopijuoti Autentifikatoriaus raktą (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Sesijos laikas baigėsi." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Ar tikrai norite atsijungti?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Tavo naujasis pagrindinis slaptažodis neatitinka politikos reikalavimų." }, - "receiveMarketingEmails": { - "message": "Gaukite „Bitwarden“ el. laiškus su skelbimais, patarimais ir tyrimų galimybėmis." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Atsisakyti prenumeratos" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Patikimas įrenginys" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Paleisk DUO ir sek veiksmus, kad baigtum prisijungti." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Kortelės duomenys" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Pridėti paskyrą" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 02688c4943f..25c58195129 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Apstiprinājuma kods ir nepieciešams." }, + "webauthnCancelOrTimeout": { + "message": "Autentifikācija tika atcelta vai tā aizņēma pārāk daudz laika. Lūgums mēģināt vēlreiz." + }, "invalidVerificationCode": { "message": "Nederīgs apstiprinājuma kods" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Nolasīt autentificētāja kvadrātkodu pašreizējā tīmekļa lapā" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Ievietot starpliktuvē autentificētāja atslēgu (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Pieteikšanās sesija ir beigusies." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Vai tiešām atteikties?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Jaunā galvenā parole neatbilst nosacījumu prasībām." }, - "receiveMarketingEmails": { - "message": "Saņemt e-pasta ziņojumus no Bitwarden par paziņojumiem, padomiem un izpētes iespējām." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Atteikt abonēšanu" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Ierīce ir uzticama" }, + "sendsNoItemsTitle": { + "message": "Nav spēkā esošu Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Send ir izmantojams, lai ar ikvienu droši kopīgotu šifrētu informāciju.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Jāievada vērtība." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Kļūda savienojuma izveidošanā ar Duo pakalpojumu. Jāizmanto cits divpakāpju pieteikšanāš veids vai jāvēršas pie Duo pēc palīdzības." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Jāpalaiž DUO un jāseko soļiem, lai pabeigtu pieteikšanos." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Kartes dati" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Pievienot kontu" + }, + "loading": { + "message": "Ielādē" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Pievienot lauku" + }, + "add": { + "message": "Pievienot" + }, + "fieldType": { + "message": "Lauka veids" + }, + "fieldLabel": { + "message": "Lauka iezīme" + }, + "textHelpText": { + "message": "Teksta lauki ir izmantojami tādai informācijai kā drošības jautājumi" + }, + "hiddenHelpText": { + "message": "Paslēptie lauki ir izmantojami tādai slepenai informācijai kā parole" + }, + "checkBoxHelpText": { + "message": "Izvēles rūtiņas ir izmantojamas, ja ir vajadzība automātiski aizpildīt veidlapas izvēles rūtiņu, piemēram, atcerēties e-pasta adresi" + }, + "linkedHelpText": { + "message": "Saistītais lauks ir izmantojams, kad noteiktā lapā tiek pieredzētas nepilnības ar automātisko aizpildi." + }, + "linkedLabelHelpText": { + "message": "Jāievada lauka HTML id, name, aria-label vai placeholder vērtība." + }, + "editField": { + "message": "Labot lauku" + }, + "editFieldLabel": { + "message": "Labot $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Izdzēst $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ pievienots", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Pārkārtot $LABEL$. Jāizmanto bultas taustiņš, lai pārvietotu vienumu augšup vai lejup.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ pārvietots augšup, $INDEX$. no $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ pārvietots lejup, $INDEX$. no $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 7d53653e2c8..3d55d60fdc3 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "പരിശോധിച്ചുറപ്പിക്കൽ കോഡ് ആവശ്യമാണ്." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "നിങ്ങളുടെ പ്രവർത്തന സമയം കഴിഞ്ഞിരിക്കുന്നു." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "നിങ്ങൾക്ക് ലോഗ് ഔട്ട് ചെയ്യണമെന്ന് ഉറപ്പാണോ?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "നിങ്ങളുടെ പുതിയ മാസ്റ്റർ പാസ്‌വേഡ് നയ ആവശ്യകതകൾ നിറവേറ്റുന്നില്ല." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 3cf67b119b2..79281e132e2 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Your login session has expired." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index fa76cf7060a..f87a68a30dc 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Your login session has expired." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 33c362d5b87..3ab1d376216 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "En verifiseringskode er påkrevd." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Ugyldig bekreftelseskode" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Din innloggingsøkt har utløpt." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Er du sikker på at du vil logge av?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Det nye hovedpassordet ditt oppfyller ikke vilkår i virksomhetsreglene." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Inndata er påkrevd." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index fa76cf7060a..f87a68a30dc 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Your login session has expired." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index b3d04e67c75..5355ce67e9d 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Verificatiecode is vereist." }, + "webauthnCancelOrTimeout": { + "message": "De authenticatie werd geannuleerd of duurde te lang. Probeer het opnieuw." + }, "invalidVerificationCode": { "message": "Ongeldige verificatiecode" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan de authenticatie-QR-code van de huidige webpagina" }, + "totpHelperTitle": { + "message": "Maak tweestapsaanmelding naadloos" + }, + "totpHelper": { + "message": "Bitwarden kan tweestapsaanmeldingscodes opslaan en invullen. Kopieer en plak de sleutel in dit veld." + }, + "totpHelperWithCapture": { + "message": "Bitwarden kan tweestapsaanmeldingscodes opslaan en invullen. Selecteer het camerapictogram om een schermafbeelding van de QR-code van deze website te maken of kopieer en plak de sleutel in dit veld." + }, "copyTOTP": { "message": "Authenticatie-sleutel (TOTP) kopiëren" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Je inlogsessie is verlopen." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Weet je zeker dat je wilt uitloggen?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Je nieuwe hoofdwachtwoord voldoet niet aan de beleidseisen." }, - "receiveMarketingEmails": { - "message": "Ontvang e-mailberichten van Bitwarden voor aankondigingen, advies en onderzoeksmogelijkheden." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Afmelden" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Vertrouwd apparaat" }, + "sendsNoItemsTitle": { + "message": "Geen actieve Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Gebruik Send voor het veilig delen van versleutelde informatie met wie dan ook.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Invoer vereist." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Fout bij het verbinden met de Duo-service. Gebruik een andere tweestapsaanmeldingsmethode of neem contact op met Duo voor hulp." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Inloggegevens" + }, + "authenticatorKey": { + "message": "Authenticatiesleutel" + }, "cardDetails": { "message": "Kaartgegevens" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Account toevoegen" + }, + "loading": { + "message": "Laden" + }, + "assign": { + "message": "Toewijzen" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Alleen organisatieleden met toegang tot deze collecties kunnen de items zien." + }, + "bulkCollectionAssignmentWarning": { + "message": "Je hebt $TOTAL_COUNT$ items geselecteerd. Je kunt $READONLY_COUNT$ items niet bijwerken omdat je geen bewerkrechten hebt.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Veld toevoegen" + }, + "add": { + "message": "Toevoegen" + }, + "fieldType": { + "message": "Veldtype" + }, + "fieldLabel": { + "message": "Veldlabel" + }, + "textHelpText": { + "message": "Gebruik tekstvelden voor data zoals beveiligingsvragen" + }, + "hiddenHelpText": { + "message": "Gebruik verborgen velden voor gevoelige gegevens zoals een wachtwoord" + }, + "checkBoxHelpText": { + "message": "Gebruik aanvinkvakjes als je een formulier automatisch wilt invullen, zoals e-mailadres herinneren" + }, + "linkedHelpText": { + "message": "Gebruik een gekoppeld veld als je problemen ervaart met het automatisch invullen voor een specifieke website." + }, + "linkedLabelHelpText": { + "message": "Html-id, naam, aria-label of placeholder van het veld invullen." + }, + "editField": { + "message": "Veld bewerken" + }, + "editFieldLabel": { + "message": "$LABEL$ bewerken", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "$LABEL$ verwijderen", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ toegevoegd", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "$LABEL$ herschikken. Gebruik de pijltjestoets om het item omhoog of omlaag te verplaatsen.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ is naar boven verplaatst, positie $INDEX$ van $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Collecties voor toewijzen selecteren" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ worden permanent overgedragen aan de geselecteerde organisatie. Je bent niet langer de eigenaar van deze items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ wordt permanent overgedragen aan $ORG$. Je bent niet langer de eigenaar van deze items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Succesvol toegewezen collecties" + }, + "nothingSelected": { + "message": "Je hebt niets geselecteerd." + }, + "movedItemsToOrg": { + "message": "Geselecteerde items verplaatst naar $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ is naar boven verplaatst, positie $INDEX$ van $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index fa76cf7060a..f87a68a30dc 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Your login session has expired." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index fa76cf7060a..f87a68a30dc 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Your login session has expired." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 1603314a616..801943de377 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -509,7 +509,7 @@ "message": "Zablokuj" }, "lockAll": { - "message": "Zablokuj wszystko" + "message": "Zablokuj wszystkie" }, "immediately": { "message": "Natychmiast" @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Kod weryfikacyjny jest wymagany." }, + "webauthnCancelOrTimeout": { + "message": "Uwierzytelnianie zostało anulowane lub trwało zbyt długo. Spróbuj ponownie." + }, "invalidVerificationCode": { "message": "Kod weryfikacyjny jest nieprawidłowy" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Zeskanuj kod QR z bieżącej strony" }, + "totpHelperTitle": { + "message": "Spraw, aby dwuetapowa weryfikacja była bezproblemowa" + }, + "totpHelper": { + "message": "Bitwarden może przechowywać i wypełniać kody weryfikacyjne. Skopiuj i wklej klucz do tego pola." + }, + "totpHelperWithCapture": { + "message": "Bitwarden może przechowywać i wypełniać kody weryfikacyjne. Wybierz ikonę aparatu, aby zrobić zrzut ekranu z kodem QR lub skopiuj i wklej klucz do tego pola." + }, "copyTOTP": { "message": "Kopiuj klucz uwierzytelniający (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Twoja sesja wygasła." }, + "logIn": { + "message": "Zaloguj się" + }, + "restartRegistration": { + "message": "Zrestartuj rejestrację" + }, + "expiredLink": { + "message": "Link wygasł" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Zrestartuj rejestrację lub spróbuj się zalogować." + }, + "youMayAlreadyHaveAnAccount": { + "message": "Możesz mieć już konto" + }, "logOutConfirmation": { "message": "Czy na pewno chcesz się wylogować?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Nowe hasło główne nie spełnia wymaganych zasad." }, - "receiveMarketingEmails": { - "message": "Otrzymuj e-maile od Bitwarden z ogłoszeniami, poradami i badaniami." + "receiveMarketingEmailsV2": { + "message": "Uzyskaj poradę, ogłoszenia i możliwości badawcze od Bitwarden w swojej skrzynce odbiorczej." }, "unsubscribe": { "message": "Anuluj subskrypcję" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Zaufano urządzeniu" }, + "sendsNoItemsTitle": { + "message": "Brak aktywnych wysyłek", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Użyj wysyłki, aby bezpiecznie dzielić się zaszyfrowanymi informacjami ze wszystkimi.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Dane wejściowe są wymagane." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Wystąpił błąd podczas połączenia z usługą Duo. Aby uzyskać pomoc, użyj innej metody dwustopniowego logowania lub skontaktuj się z Duo." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Uruchom DUO i wykonaj kroki, aby zakończyć logowanie." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Dane logowania" + }, + "authenticatorKey": { + "message": "Klucz uwierzytelniający" + }, "cardDetails": { "message": "Szczegóły karty" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Dodaj konto" + }, + "loading": { + "message": "Wczytywanie" + }, + "assign": { + "message": "Przypisz" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Tylko członkowie organizacji z dostępem do tych kolekcji będą mogli zobaczyć te elementy." + }, + "bulkCollectionAssignmentWarning": { + "message": "Wybrałeś $TOTAL_COUNT$ elementów. Nie możesz zaktualizować $READONLY_COUNT$ elementów, ponieważ nie masz uprawnień do edycji.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Dodaj pole" + }, + "add": { + "message": "Dodaj" + }, + "fieldType": { + "message": "Typ pola" + }, + "fieldLabel": { + "message": "Etykieta pola" + }, + "textHelpText": { + "message": "Użyj pól tekstowych dla danych takich jak pytania bezpieczeństwa" + }, + "hiddenHelpText": { + "message": "Użyj ukrytych pól dla danych poufnych, takich jak hasło" + }, + "checkBoxHelpText": { + "message": "Użyj pól wyboru, jeśli chcesz automatycznie wypełnić pole wyboru formularza, np. zapamiętaj e-mail" + }, + "linkedHelpText": { + "message": "Użyj powiązanego pola, gdy masz problemy z autouzupełnianiem na konkretnej stronie internetowej." + }, + "linkedLabelHelpText": { + "message": "Wprowadź atrybut z HTML'a: id, name, aria-label lub placeholder." + }, + "editField": { + "message": "Edytuj pole" + }, + "editFieldLabel": { + "message": "Edytuj $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Usuń $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "Dodano $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Zmień kolejność $LABEL$. Użyj klawiszy że strzałkami aby przenieść element w górę lub w dół.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ przeniósł się w górę, pozycja $INDEX$ z $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Wybierz kolekcje do przypisania" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ zostanie trwale przeniesiony do wybranej organizacji. Nie będziesz już posiadać tych elementów.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ zostanie trwale przeniesiony do $ORG$. Nie będziesz już właścicielem tych elementów.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Pomyślnie przypisano kolekcje" + }, + "nothingSelected": { + "message": "Nie zaznaczyłeś żadnych elementów." + }, + "movedItemsToOrg": { + "message": "Zaznaczone elementy zostały przeniesione do $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ przeniósł się w dół, pozycja $INDEX$ z $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index a9161bde22c..4372e973ab8 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "O código de verificação é necessário." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Código de verificação inválido" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Escaneie o código QR do autenticador na página atual" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copiar chave de Autenticação (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "A sua sessão expirou." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Você tem certeza que deseja sair?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "A sua nova senha mestra não cumpre aos requisitos da política." }, - "receiveMarketingEmails": { - "message": "Obtenha e-mails do Bitwarden para anúncios, conselhos e oportunidades de pesquisa." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Cancelar subscrição" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Dispositivo confiável" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Entrada necessária." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Inicie o Duo e siga os passos para finalizar o login." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index dc64dfa8a7d..2ba56d3a922 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "É necessário o código de verificação." }, + "webauthnCancelOrTimeout": { + "message": "A autenticação foi cancelada ou demorou demasiado tempo. Por favor, tente novamente." + }, "invalidVerificationCode": { "message": "Código de verificação inválido" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Digitalize o código QR do autenticador a partir da página Web atual" }, + "totpHelperTitle": { + "message": "Torne a verificação de dois passos simples" + }, + "totpHelper": { + "message": "O Bitwarden pode armazenar e preencher códigos de verificação de dois passos. Copie e cole a chave neste campo." + }, + "totpHelperWithCapture": { + "message": "O Bitwarden pode armazenar e preencher códigos de verificação de dois passos. Selecione o ícone da câmara para tirar uma captura de ecrã do código QR do autenticador deste site ou copie e cole a chave neste campo." + }, "copyTOTP": { "message": "Copiar Chave de autenticação (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "A sua sessão expirou." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Tem a certeza de que pretende terminar sessão?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "A sua nova palavra-passe mestra não cumpre os requisitos da política." }, - "receiveMarketingEmails": { - "message": "Receba e-mails do Bitwarden com anúncios, conselhos e oportunidades de investigação." + "receiveMarketingEmailsV2": { + "message": "Receba conselhos, anúncios e oportunidades de investigação do Bitwarden na sua caixa de entrada." }, "unsubscribe": { "message": "Anular subscrição" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Dispositivo de confiança" }, + "sendsNoItemsTitle": { + "message": "Sem Sends ativos", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Utilize o Send para partilhar de forma segura informações encriptadas com qualquer pessoa.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Campo obrigatório." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Erro ao ligar ao serviço Duo. Utilize um método de verificação de dois passos diferente ou contacte o Duo para obter assistência." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Inicie o Duo e siga os passos para concluir o início de sessão." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Credenciais de início de sessão" + }, + "authenticatorKey": { + "message": "Chave de autenticação" + }, "cardDetails": { "message": "Detalhes do cartão" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Adicionar conta" + }, + "loading": { + "message": "A carregar" + }, + "assign": { + "message": "Atribuir" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Apenas os membros da organização com acesso a estas coleções poderão ver os itens." + }, + "bulkCollectionAssignmentWarning": { + "message": "Selecionou $TOTAL_COUNT$ itens. Não pode atualizar $READONLY_COUNT$ dos itens porque não tem permissões de edição.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Adicionar campo" + }, + "add": { + "message": "Adicionar" + }, + "fieldType": { + "message": "Tipo de campo" + }, + "fieldLabel": { + "message": "Etiqueta do campo" + }, + "textHelpText": { + "message": "Utilize campos de texto para dados como perguntas de segurança" + }, + "hiddenHelpText": { + "message": "Utilize campos ocultos para dados sensíveis como uma palavra-passe" + }, + "checkBoxHelpText": { + "message": "Utilize caixas de verificação se pretender preencher automaticamente uma caixa de verificação de um formulário, como um e-mail de memorização" + }, + "linkedHelpText": { + "message": "Utilize um campo ligado quando tiver problemas de preenchimento automático para um site específico." + }, + "linkedLabelHelpText": { + "message": "Introduza o ID do HTML, o nome, a aria-label ou o placeholder do campo." + }, + "editField": { + "message": "Editar campo" + }, + "editFieldLabel": { + "message": "Editar $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Eliminar $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ adicionado", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reordenar $LABEL$. Utilize a tecla de seta para mover o item para cima ou para baixo.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ movido para cima, posição $INDEX$ de $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Selecione as coleções a atribuir" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ serão permanentemente transferidos para a organização selecionada. Estes itens deixarão de lhe pertencer.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ serão permanentemente transferidos para $ORG$. Deixará de ser proprietário destes itens.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Coleções atribuídas com sucesso" + }, + "nothingSelected": { + "message": "Não selecionou nada." + }, + "movedItemsToOrg": { + "message": "Itens selecionados movidos para $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ movido para baixo, posição $INDEX$ de $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 3b8809f22dc..bc4dad01f6c 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Este necesar codul de verificare." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Cod de verificare nevalid" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Sesiunea de autentificare a expirat." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Sigur doriți să vă deconectați?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Noua dvs. parolă principală nu îndeplinește cerințele politicii." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Dispozitiv de încredere" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Este necesară o intrare." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index e5b303c7ea6..0138c5e0b06 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Необходимо ввести код подтверждения." }, + "webauthnCancelOrTimeout": { + "message": "Аутентификация была отменена или заняла слишком много времени. Пожалуйста, попробуйте еще раз." + }, "invalidVerificationCode": { "message": "Неверный код подтверждения" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Сканировать QR-код аутентификатора с текущей веб-страницы" }, + "totpHelperTitle": { + "message": "Сделать двухэтапную аутентификацию бесшовной" + }, + "totpHelper": { + "message": "Bitwarden может хранить и заполнять коды двухэтапной аутентификации. Скопируйте и вставьте ключ в это поле." + }, + "totpHelperWithCapture": { + "message": "Bitwarden может хранить и заполнять коды двухэтапной аутентификации. Выберите значок камеры, чтобы сделать скриншот QR-кода этого сайта, или скопируйте и вставьте ключ в это поле." + }, "copyTOTP": { "message": "Скопировать ключ аутентификатора (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Истек срок действия вашего сеанса." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Вы действительно хотите выйти?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Ваш новый мастер-пароль не соответствует требованиям политики." }, - "receiveMarketingEmails": { - "message": "Получайте электронные письма от Bitwarden с анонсами, советами и возможностями для исследований." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Отписаться" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Доверенное устройство" }, + "sendsNoItemsTitle": { + "message": "Нет активных Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Используйте Send для безопасного обмена зашифрованной информацией с кем угодно.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Необходимо ввести данные." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Ошибка при подключении к сервису Duo. Используйте другой метод двухэтапной аутентификации или обратитесь за помощью в Duo." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Запустите Duo и следуйте шагам для завершения авторизации." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Данные для авторизации" + }, + "authenticatorKey": { + "message": "Ключ аутентификатора" + }, "cardDetails": { "message": "Реквизиты карты" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Добавить аккаунт" + }, + "loading": { + "message": "Загрузка" + }, + "assign": { + "message": "Назначить" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Только члены организации, имеющие доступ к этим коллекциям, смогут видеть элементы." + }, + "bulkCollectionAssignmentWarning": { + "message": "Вы выбрали $TOTAL_COUNT$ элемента(-ов). Вы не можете обновить $READONLY_COUNT$ элемента(-ов), поскольку у вас нет прав на редактирование.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Добавить поле" + }, + "add": { + "message": "Добавить" + }, + "fieldType": { + "message": "Тип поля" + }, + "fieldLabel": { + "message": "Метка поля" + }, + "textHelpText": { + "message": "Используйте текстовые поля для простых данных, таких как контрольные вопросы" + }, + "hiddenHelpText": { + "message": "Используйте скрытые поля для конфиденциальных данных, таких как пароли" + }, + "checkBoxHelpText": { + "message": "Используйте флажки, если вы хотите автоматически заполнить поле формы, например, email" + }, + "linkedHelpText": { + "message": "Используйте связанное поле, если у вас возникли проблемы с автозаполнением для конкретного сайта." + }, + "linkedLabelHelpText": { + "message": "Введите HTML-идентификатор поля, имя, aria-label, или плейсхолдер." + }, + "editField": { + "message": "Изменить поле" + }, + "editFieldLabel": { + "message": "Изменить $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Удалить $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ добавлен(о)", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Изменить порядок $LABEL$. Используйте клавиши курсора для перемещения элемента вверх или вниз.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ перемещено вверх, позиция $INDEX$ $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Выбрать коллекции для назначения" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ будут навсегда переданы выбранной организации. Вы больше не будете владельцем этих элементов.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ будут навсегда переданы $ORG$. Вы больше не будете владельцем этих элементов.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Коллекции успешно назначены" + }, + "nothingSelected": { + "message": "Вы ничего не выбрали." + }, + "movedItemsToOrg": { + "message": "Выбранные элементы перемещены в $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ перемещено вниз, позиция $INDEX$ $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 1416bea9582..994c2891add 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "සත්යාපන කේතය අවශ්ය වේ." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "වලංගු නොවන සත්යාපන කේතය" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "ඔබගේ පිවිසුම් සැසිය කල් ඉකුත් වී ඇත." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "ඔබට ලොග් වීමට අවශ්ය බව ඔබට විශ්වාසද?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "ඔබගේ නව ප්රධාන මුරපදය ප්රතිපත්ති අවශ්යතා සපුරාලන්නේ නැත." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index c375af5ffa9..127888ca2c6 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -242,7 +242,7 @@ "message": "Bitwarden Authenticator" }, "continueToAuthenticatorPageDesc": { - "message": "Bitwarden Authenticator umožňuje uložiť overovacie kľúče a generovať kódy TOTP pre dvojstupňoveé overovanie. Viac informácií nájdete na webovej stránke bitwarden.com" + "message": "Bitwarden Authenticator umožňuje uložiť overovacie kľúče a generovať kódy TOTP pre dvojstupňové overovanie. Viac informácií nájdete na webovej stránke bitwarden.com" }, "bitwardenSecretsManager": { "message": "Bitwarden Secrets Manager" @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Overovací kód je povinný." }, + "webauthnCancelOrTimeout": { + "message": "Overenie bolo zrušené alebo trvalo príliš dlho. Skúste to znova." + }, "invalidVerificationCode": { "message": "Neplatný verifikačný kód" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Naskenovať QR kód overovateľa z aktuálnej webovej stránky" }, + "totpHelperTitle": { + "message": "Spravte dvojstupňové overenie bezproblémovým" + }, + "totpHelper": { + "message": "Bitwarden umožňuje uložiť a vyplniť kódy dvojstupňového overenia. Skopírujte a vložte kľúč do tohto poľa." + }, + "totpHelperWithCapture": { + "message": "Bitwarden umožňuje uložiť a vyplniť kódy dvojstupňového overenia. Vyberte ikonu fotoaparátu a zosnímajte obrazovku QR kódu overovacej aplikácie tejto webovej stránky alebo skopírujte a vložte kľúč do tohto poľa." + }, "copyTOTP": { "message": "Kopírovať kľúč overovateľa (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Vaša relácia vypršala." }, + "logIn": { + "message": "Prihlásiť sa" + }, + "restartRegistration": { + "message": "Zopakovať registráciu" + }, + "expiredLink": { + "message": "Platnosť odkazu vypršala" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Prosím zopakujte registráciu alebo sa pokúste prihlásiť." + }, + "youMayAlreadyHaveAnAccount": { + "message": "Možno už máte účet" + }, "logOutConfirmation": { "message": "Naozaj sa chcete odhlásiť?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Vaše nové heslo nespĺňa pravidlá." }, - "receiveMarketingEmails": { - "message": "Dostávať e-maily od Bitwardenu s oznámeniami, radami a možnosťami výskumu." + "receiveMarketingEmailsV2": { + "message": "Dostávajte do schránky rady, oznámenia a príležitosti na výskum od spoločnosti Bitwarden." }, "unsubscribe": { "message": "Odhlásiť sa z odberu" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Dôveryhodné zariadenie" }, + "sendsNoItemsTitle": { + "message": "Žiadne aktívne Sendy", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Použite Send na bezpečné zdieľanie zašifrovaných informácii s kýmkoľvek.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Vstup je povinný." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Chyba pri pripájaní k službe Duo. Použite inú metódu dvojstupňového prihlásenia alebo kontaktujte Duo a požiadajte o pomoc." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Spustite DUO a postupujte podľa pokynov na dokončenie prihlásenia." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Prihlasovacie údaje" + }, + "authenticatorKey": { + "message": "Kľúč overovacej aplikácie" + }, "cardDetails": { "message": "Podrobnosti o karte" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Pridať účet" + }, + "loading": { + "message": "Načíta sa" + }, + "assign": { + "message": "Priradiť" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Položky si budú môcť pozrieť len členovia organizácie s prístupom k týmto zbierkam." + }, + "bulkCollectionAssignmentWarning": { + "message": "Vybrali ste $TOTAL_COUNT$ položky. Nemôžete aktualizovať $READONLY_COUNT$ položky(-iek), pretože nemáte oprávnenie na úpravu.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Pridať pole" + }, + "add": { + "message": "Pridať" + }, + "fieldType": { + "message": "Typ poľa" + }, + "fieldLabel": { + "message": "Názov poľa" + }, + "textHelpText": { + "message": "Textové polia používajte pre také údaje, ako sú bezpečnostné otázky" + }, + "hiddenHelpText": { + "message": "Skryté polia požívajte pre citlivé údaje ako je heslo" + }, + "checkBoxHelpText": { + "message": "Ak chcete automaticky vyplniť začiarkávacie políčko formulára, napríklad zapamätať e-mail, použite začiarkávacie políčka" + }, + "linkedHelpText": { + "message": "Ak máte problémy s automatickým vypĺňaním pre konkrétnu webovú stránku, použite prepojené pole." + }, + "linkedLabelHelpText": { + "message": "Zadajte html atribút poľa id, name, aria-label, alebo placeholder." + }, + "editField": { + "message": "Upraviť pole" + }, + "editFieldLabel": { + "message": "Upraviť $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Odstrániť $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "Pridané $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Zmeniť poradie $LABEL$. Na presun položky hore alebo dole použite klávesy so šípkami.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ presunuté vyššie, pozícia $INDEX$/$LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Vyberte zbierky na priradenie" + }, + "personalItemsTransferWarning": { + "message": "Do vybranej organizácie sa natrvalo presunú $PERSONAL_ITEMS_COUNT$. Tieto položky už nebudete vlastniť.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "Do $ORG$ sa natrvalo presunú $PERSONAL_ITEMS_COUNT$. Tieto položky už nebudete vlastniť.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Zbierky boli úspešne priradené" + }, + "nothingSelected": { + "message": "Nič ste nevybrali." + }, + "movedItemsToOrg": { + "message": "Vybraté položky boli presunuté do $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ presunuté nižšie, pozícia $INDEX$/$LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index e53e830b006..32adb83689c 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Koda za preverjanje je obvezna." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Neveljavna koda za preverjanje" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Vaša seja je potekla." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Ste prepričani, da se želite odjaviti?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Vaše novo glavno geslo ne ustreza zahtevam." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 02a3f824521..b2d11120381 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Верификациони код је обавезан." }, + "webauthnCancelOrTimeout": { + "message": "Аутентификација је отказана или је трајала предуго. Молим вас, покушајте поново." + }, "invalidVerificationCode": { "message": "Неисправан верификациони код" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Скенирајте QR кôд аутентификатора са тренутне веб странице" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Копирати једнократни кôд (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Ваша сесија је истекла." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Заиста желите да се одјавите?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Ваша нова главна лозинка не испуњава захтеве смерница." }, - "receiveMarketingEmails": { - "message": "Добијајте е-пошту од Bitwarden-а за најаве, савете и могућности истраживања." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Одјави се" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Уређај поуздан" }, + "sendsNoItemsTitle": { + "message": "Нема активних Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Употребите Send да безбедно делите шифроване информације са било ким.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Унос је потребан." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Грешка при повезивању са услугом Duo. Користите други метод пријаве у два корака или контактирајте Duo за помоћ." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Покренути DUO и пратите кораке да бисте завршили пријављивање." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Детаљи картице" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Додај налог" + }, + "loading": { + "message": "Учитавање" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Додај поље" + }, + "add": { + "message": "Додај" + }, + "fieldType": { + "message": "Врста поља" + }, + "fieldLabel": { + "message": "Ознака поља" + }, + "textHelpText": { + "message": "Користите текстуална поља за податке као што су безбедносна питања" + }, + "hiddenHelpText": { + "message": "Користите скривена поља за осетљиве податке као што је лозинка" + }, + "checkBoxHelpText": { + "message": "Користите поља за потврду ако желите да аутоматски попуните поље за потврду обрасца, на пример имејл за памћење" + }, + "linkedHelpText": { + "message": "Користите повезано поље када имате проблема са аутоматским попуњавањем за одређену веб локацију." + }, + "linkedLabelHelpText": { + "message": "Унесите html Ид поља, име, aria-label, или placeholder." + }, + "editField": { + "message": "Уреди поље" + }, + "editFieldLabel": { + "message": "Уреди $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Обриши $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ је додато", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Преместити $LABEL$. Користите тастер са стрелицом да бисте померили ставку.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ премештено на горе, позиција $INDEX$ од $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ премештено на доле, позиција $INDEX$ од $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 3444a42c364..8c4693e7008 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Verifieringskod krävs." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Ogiltig verifieringskod" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Kopiera autentiseringsnyckel (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Din inloggningssession har upphört." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Är du säker på att du vill logga ut?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Ditt nya huvudlösenord uppfyller inte kraven i policyn." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Enhet betrodd" }, + "sendsNoItemsTitle": { + "message": "Inga aktiva Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Inmatning är obligatoriskt." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Starta Duo och följ stegen för att slutföra inloggningen." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Lägg till konto" + }, + "loading": { + "message": "Laddar" + }, + "assign": { + "message": "Tilldela" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Endast organisationsmedlemmar med tillgång till dessa samlingar kommer att kunna se objekten." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Lägg till fält" + }, + "add": { + "message": "Lägg till" + }, + "fieldType": { + "message": "Fälttyp" + }, + "fieldLabel": { + "message": "Fältetikett" + }, + "textHelpText": { + "message": "Använd textfält för data som t. ex. säkerhetsfrågor" + }, + "hiddenHelpText": { + "message": "Använd dolda fält för känslig data, som t. ex. ett lösenord" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Redigera fält" + }, + "editFieldLabel": { + "message": "Redigera $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Radera $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index fa76cf7060a..f87a68a30dc 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Your login session has expired." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 8cd62631f36..061bb877c19 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "ต้องระบุโค้ดยืนยัน" }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "โค้ดยืนยันไม่ถูกต้อง" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Scan authenticator QR code from current webpage" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Copy Authenticator key (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "เซสชันของคุณหมดอายุแล้ว" }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "คุณต้องการล็อกเอาต์ใช่หรือไม่?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "รหัสผ่านหลักใหม่ของคุณไม่เป็นไปตามข้อกำหนดของนโยบาย" }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 4af23e28a36..4296f2bf007 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Doğrulama kodu gereklidir." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Geçersiz doğrulama kodu" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Mevcut web sayfasındaki kimlik doğrulayıcı QR kodunu tarayın" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Kimlik doğrulama anahtarını kopyala (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Oturumunuz zaman aşımına uğradı." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Çıkış yapmak istediğinize emin misiniz?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Yeni ana parolanız ilke gereksinimlerini karşılamıyor." }, - "receiveMarketingEmails": { - "message": "Bitwarden'dan duyurular, öneriler ve araştırmalarla ilgili e-postalar alın." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "İstediğiniz zaman" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Cihaza güvenildi" }, + "sendsNoItemsTitle": { + "message": "Aktif Send yok", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Şifrelenmiş bilgileri güvenle paylaşmak için Send'i kullanabilirsiniz.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Girdi gerekli." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Duo'yu başlatın ve oturum açmayı tamamlamak için adımları izleyin." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Hesap bilgileri" + }, + "authenticatorKey": { + "message": "Kimlik doğrulama anahtarı" + }, "cardDetails": { "message": "Kart bilgileri" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "Hesap ekle" + }, + "loading": { + "message": "Yükleniyor" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index fe9fde369ac..6826e6fee14 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Потрібний код підтвердження." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Недійсний код підтвердження" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Скануйте QR-код програмою автентифікації" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Скопіюйте ключ автентифікації (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Тривалість вашого сеансу завершилась." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Ви дійсно хочете вийти?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Ваш новий головний пароль не задовольняє вимоги політики." }, - "receiveMarketingEmails": { - "message": "Отримуйте електронні листи від Bitwarden з оголошеннями, порадами та інформацією про нові можливості." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Відписатися" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Довірений пристрій" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Необхідно ввести дані." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Запустіть Duo і виконайте дії для завершення входу." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Подробиці картки" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 3210c43acbf..44ca71922c2 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "Yêu cầu mã xác nhận." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Mã xác minh không đúng" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "Quét mã QR xác thực từ trang web hiện tại" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "Sao chép khóa Authenticator (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "Phiên đăng nhập của bạn đã hết hạn." }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Bạn có chắc chắn muốn đăng xuất không?" }, @@ -889,7 +916,7 @@ "message": "Xác nhận xuất kho lưu trữ" }, "exportWarningDesc": { - "message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it." + "message": "Bản xuất này chứa dữ liệu kho bạn và không được mã hóa. Bạn không nên lưu trữ hay gửi tập tin đã xuất thông qua phương thức rủi ro (như email). Vui lòng xóa nó ngay lập tức khi bạn đã sử dụng xong." }, "encExportKeyWarningDesc": { "message": "Quá trình xuất này sẽ mã hóa dữ liệu của bạn bằng khóa mã hóa của tài khoản. Nếu bạn từng xoay khóa mã hóa tài khoản của mình, bạn nên xuất lại vì bạn sẽ không thể giải mã tệp xuất này." @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Mật khẩu chính bạn chọn không đáp ứng yêu cầu." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -1959,15 +1986,15 @@ } }, "send": { - "message": "Send", + "message": "Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "searchSends": { - "message": "Tìm kiếm Send", + "message": "Tìm kiếm mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "addSend": { - "message": "Thêm Send", + "message": "Thêm mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { @@ -1977,11 +2004,11 @@ "message": "Tập tin" }, "allSends": { - "message": "Toàn bộ Send", + "message": "Tất cả mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCountReached": { - "message": "Đã đạt đến số lượng truy cập tối đa", + "message": "Đã vượt số lần truy cập tối đa", "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "expired": { @@ -1994,7 +2021,7 @@ "message": "Mật khẩu đã được bảo vệ" }, "copySendLink": { - "message": "Sao chép liên kết Send", + "message": "Sao chép liên kết mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "removePassword": { @@ -2007,11 +2034,11 @@ "message": "Đã xóa mật khẩu" }, "deletedSend": { - "message": "Đã xóa Send", + "message": "Đã xóa mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLink": { - "message": "Gửi liên kết", + "message": "Liên kết Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disabled": { @@ -2021,15 +2048,15 @@ "message": "Bạn có chắc chắn muốn xóa mật khẩu này không?" }, "deleteSend": { - "message": "Xóa Send", + "message": "Xóa mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendConfirmation": { - "message": "Bạn có chắc chắn muốn xóa Send này?", + "message": "Bạn có chắc muốn mục Gửi này?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { - "message": "Chỉnh sửa Send", + "message": "Sửa mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeHeader": { @@ -2047,14 +2074,14 @@ "message": "Ngày xóa" }, "deletionDateDesc": { - "message": "Send sẽ được xóa vĩnh viễn vào ngày và giờ được chỉ định.", + "message": "Mục Gửi sẽ được xóa vĩnh viễn vào ngày và giờ chỉ định.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { "message": "Ngày hết hạn" }, "expirationDateDesc": { - "message": "Nếu được thiết lập, truy cập vào Send này sẽ hết hạn vào ngày và giờ được chỉ định.", + "message": "Nếu được thiết lập, mục Gửi này sẽ hết hạn vào ngày và giờ được chỉ định.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "oneDay": { @@ -2076,11 +2103,11 @@ "message": "Số lượng truy cập tối đa" }, "maximumAccessCountDesc": { - "message": "Nếu được thiết lập, khi đã đạt tới số lượng truy cập tối đa, người dùng sẽ không thể truy cập Send này nữa.", + "message": "Nếu được thiết lập, khi đã đạt tới số lượng truy cập tối đa, người dùng sẽ không thể truy cập mục Gửi này nữa.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendPasswordDesc": { - "message": "Tùy chọn yêu cầu mật khẩu để người dùng truy cập Gửi này.", + "message": "Yêu cầu nhập mật khẩu khi người dùng truy cập vào phần Gửi này.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendNotesDesc": { @@ -2117,15 +2144,15 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "Do chính sách doanh nghiệp, bạn chỉ có thể xóa những Send hiện có.", + "message": "Do chính sách doanh nghiệp, bạn chỉ có thể xóa những mục Gửi hiện có.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Đã tạo Send", + "message": "Đã tạo mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Đã chỉnh sửa Send", + "message": "Đã lưu mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "Device trusted" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index afc990ea68d..28f6419afa9 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "必须填写验证码。" }, + "webauthnCancelOrTimeout": { + "message": "身份验证被取消或耗时过长。请重试。" + }, "invalidVerificationCode": { "message": "无效的验证码" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "从当前网页扫描验证器二维码" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "复制验证器密钥 (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "您的登录会话已过期。" }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "确定要注销吗?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "您的新主密码不符合策略要求。" }, - "receiveMarketingEmails": { - "message": "接收来自 Bitwarden 的电子邮件,以获取公告、建议和调研。" + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "取消订阅" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "设备已信任" }, + "sendsNoItemsTitle": { + "message": "没有活跃的 Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "使用 Send 与任何人安全地分享加密信息。", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "必须输入内容。" }, @@ -2998,7 +3033,7 @@ } }, "tryAgain": { - "message": "再试一次" + "message": "请重试" }, "verificationRequiredForActionSetPinToContinue": { "message": "此操作需要验证。设置一个 PIN 码以继续。" @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "与 Duo 服务连接时出错。使用不同的两步登录方式或联系 Duo 寻求帮助。" + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "启动 DUO 并按照步骤完成登录。" }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "支付卡详情" }, @@ -3618,6 +3662,167 @@ } }, "addAccount": { - "message": "Add account" + "message": "添加账户" + }, + "loading": { + "message": "加载中" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "添加字段" + }, + "add": { + "message": "添加" + }, + "fieldType": { + "message": "字段类型" + }, + "fieldLabel": { + "message": "字段标签" + }, + "textHelpText": { + "message": "文本型字段用于比如安全问题之类的数据" + }, + "hiddenHelpText": { + "message": "隐藏型字段用于比如密码之类的敏感数据" + }, + "checkBoxHelpText": { + "message": "如果您想自动勾选表单复选框(例如记住电子邮件地址),请勾选复选框" + }, + "linkedHelpText": { + "message": "当您处理特定网站的自动填充问题时,请使用链接型字段。" + }, + "linkedLabelHelpText": { + "message": "输入字段的 html id、名称、aria-label 或占位符。" + }, + "editField": { + "message": "编辑字段" + }, + "editFieldLabel": { + "message": "编辑 $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "删除 $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ 已添加", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "重新排序 $LABEL$。使用方向键向上或向下移动项目。", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ 已上移,位置 $INDEX$ / $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ 已下移,位置 $INDEX$ / $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 37436357245..c5ac6a6d52e 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -611,6 +611,9 @@ "verificationCodeRequired": { "message": "必須填入驗證碼。" }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "無效的驗證碼" }, @@ -636,6 +639,15 @@ "totpCapture": { "message": "從目前網頁掃描驗證器二維碼" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "copyTOTP": { "message": "複製驗證器金鑰 (TOTP)" }, @@ -648,6 +660,21 @@ "loginExpired": { "message": "您的登入階段已過期。" }, + "logIn": { + "message": "Log in" + }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "您確定要登出嗎?" }, @@ -1820,8 +1847,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "您新的主密碼不符合原則要求。" }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2762,6 +2789,14 @@ "deviceTrusted": { "message": "裝置已信任" }, + "sendsNoItemsTitle": { + "message": "No active Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsNoItemsMessage": { + "message": "Use Send to securely share encrypted information with anyone.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "必須輸入內容。" }, @@ -3045,6 +3080,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "啟動 Duo 並依照步驟完成登入。" }, @@ -3605,6 +3643,12 @@ } } }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "cardDetails": { "message": "Card details" }, @@ -3619,5 +3663,166 @@ }, "addAccount": { "message": "Add account" + }, + "loading": { + "message": "Loading" + }, + "assign": { + "message": "Assign" + }, + "bulkCollectionAssignmentDialogDescription": { + "message": "Only organization members with access to these collections will be able to see the items." + }, + "bulkCollectionAssignmentWarning": { + "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "placeholders": { + "total_count": { + "content": "$1", + "example": "10" + }, + "readonly_count": { + "content": "$2" + } + } + }, + "addField": { + "message": "Add field" + }, + "add": { + "message": "Add" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to auto-fill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing auto-fill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, + "editField": { + "message": "Edit field" + }, + "editFieldLabel": { + "message": "Edit $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "deleteCustomField": { + "message": "Delete $LABEL$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "fieldAdded": { + "message": "$LABEL$ added", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderToggleButton": { + "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + } + } + }, + "reorderFieldUp": { + "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } + }, + "selectCollectionsToAssign": { + "message": "Select collections to assign" + }, + "personalItemsTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + } + } + }, + "personalItemsWithOrgTransferWarning": { + "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "placeholders": { + "personal_items_count": { + "content": "$1", + "example": "2 items" + }, + "org": { + "content": "$2", + "example": "Organization name" + } + } + }, + "successfullyAssignedCollections": { + "message": "Successfully assigned collections" + }, + "nothingSelected": { + "message": "You have not selected anything." + }, + "movedItemsToOrg": { + "message": "Selected items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "reorderFieldDown": { + "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "placeholders": { + "label": { + "content": "$1", + "example": "Custom field" + }, + "index": { + "content": "$2", + "example": "1" + }, + "length": { + "content": "$3", + "example": "3" + } + } } } diff --git a/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts b/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts new file mode 100644 index 00000000000..af1d0d7767a --- /dev/null +++ b/apps/browser/src/auth/popup/two-factor-auth-duo.component.ts @@ -0,0 +1,105 @@ +import { DialogModule } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { ReactiveFormsModule, FormsModule } from "@angular/forms"; +import { Subject, Subscription, filter, firstValueFrom, takeUntil } from "rxjs"; + +import { TwoFactorAuthDuoComponent as TwoFactorAuthDuoBaseComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-duo.component"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ToastService } from "@bitwarden/components"; + +import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions"; +import { ButtonModule } from "../../../../../libs/components/src/button"; +import { FormFieldModule } from "../../../../../libs/components/src/form-field"; +import { LinkModule } from "../../../../../libs/components/src/link"; +import { I18nPipe } from "../../../../../libs/components/src/shared/i18n.pipe"; +import { TypographyModule } from "../../../../../libs/components/src/typography"; +import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service"; + +@Component({ + standalone: true, + selector: "app-two-factor-auth-duo", + templateUrl: + "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.html", + imports: [ + CommonModule, + JslibModule, + DialogModule, + ButtonModule, + LinkModule, + TypographyModule, + ReactiveFormsModule, + FormFieldModule, + AsyncActionsModule, + FormsModule, + ], + providers: [I18nPipe], +}) +export class TwoFactorAuthDuoComponent extends TwoFactorAuthDuoBaseComponent { + private destroy$ = new Subject(); + duoResultSubscription: Subscription; + + constructor( + protected i18nService: I18nService, + protected platformUtilsService: PlatformUtilsService, + private browserMessagingApi: ZonedMessageListenerService, + private environmentService: EnvironmentService, + toastService: ToastService, + ) { + super(i18nService, platformUtilsService, toastService); + } + + async ngOnInit(): Promise { + await super.ngOnInit(); + } + + async ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + protected override setupDuoResultListener() { + if (!this.duoResultSubscription) { + this.duoResultSubscription = this.browserMessagingApi + .messageListener$() + .pipe( + filter((msg: any) => msg.command === "duoResult"), + takeUntil(this.destroy$), + ) + .subscribe((msg: { command: string; code: string; state: string }) => { + this.token.emit(msg.code + "|" + msg.state); + }); + } + } + + override async launchDuoFrameless() { + if (this.duoFramelessUrl === null) { + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"), + }); + return; + } + const duoHandOffMessage = { + title: this.i18nService.t("youSuccessfullyLoggedIn"), + message: this.i18nService.t("youMayCloseThisWindow"), + isCountdown: false, + }; + + // we're using the connector here as a way to set a cookie with translations + // before continuing to the duo frameless url + const env = await firstValueFrom(this.environmentService.environment$); + const launchUrl = + env.getWebVaultUrl() + + "/duo-redirect-connector.html" + + "?duoFramelessUrl=" + + encodeURIComponent(this.duoFramelessUrl) + + "&handOffMessage=" + + encodeURIComponent(JSON.stringify(duoHandOffMessage)); + this.platformUtilsService.launchUri(launchUrl); + } +} diff --git a/apps/browser/src/auth/popup/two-factor-auth.component.ts b/apps/browser/src/auth/popup/two-factor-auth.component.ts index d2a1ba20bff..9ae1f088258 100644 --- a/apps/browser/src/auth/popup/two-factor-auth.component.ts +++ b/apps/browser/src/auth/popup/two-factor-auth.component.ts @@ -42,6 +42,7 @@ import { import { BrowserApi } from "../../platform/browser/browser-api"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; +import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; import { TwoFactorAuthEmailComponent } from "./two-factor-auth-email.component"; @Component({ @@ -65,6 +66,7 @@ import { TwoFactorAuthEmailComponent } from "./two-factor-auth-email.component"; TwoFactorAuthEmailComponent, TwoFactorAuthAuthenticatorComponent, TwoFactorAuthYubikeyComponent, + TwoFactorAuthDuoComponent, TwoFactorAuthWebAuthnComponent, ], providers: [I18nPipe], diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 462acb818b8..3c67872e238 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -38,6 +38,18 @@ export type FocusedFieldData = { frameId?: number; }; +export type InlineMenuElementPosition = { + top: number; + left: number; + width: number; + height: number; +}; + +export type InlineMenuPosition = { + button?: InlineMenuElementPosition; + list?: InlineMenuElementPosition; +}; + export type OverlayAddNewItemMessage = { login?: { uri?: string; @@ -120,6 +132,7 @@ export type OverlayBackgroundExtensionMessageHandlers = { message, sender, }: BackgroundOnMessageHandlerParams) => Promise; + getAutofillInlineMenuPosition: () => InlineMenuPosition; updateAutofillInlineMenuElementIsVisibleStatus: ({ message, sender, diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 81a7754f84b..41d9d8ec32e 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -319,7 +319,8 @@ describe("OverlayBackground", () => { }); describe("removing pageDetails", () => { - it("removes the page details and port key for a specific tab from the pageDetailsForTab object", () => { + it("removes the page details and port key for a specific tab from the pageDetailsForTab object", async () => { + await initOverlayElementPorts(); const tabId = 1; sendMockExtensionMessage( { command: "collectPageDetailsResponse", details: createAutofillPageDetailsMock() }, @@ -1402,6 +1403,25 @@ describe("OverlayBackground", () => { }); }); + describe("getAutofillInlineMenuPosition", () => { + it("returns the current inline menu position", async () => { + overlayBackground["inlineMenuPosition"] = { + button: { left: 1, top: 2, width: 3, height: 4 }, + }; + + sendMockExtensionMessage( + { command: "getAutofillInlineMenuPosition" }, + mock(), + sendResponse, + ); + await flushPromises(); + + expect(sendResponse).toHaveBeenCalledWith({ + button: { left: 1, top: 2, width: 3, height: 4 }, + }); + }); + }); + it("triggers a debounced reposition of the inline menu if the sender frame has a `null` sub frame offsets value", async () => { jest.useFakeTimers(); const focusedFieldData = createFocusedFieldDataMock(); @@ -2095,6 +2115,22 @@ describe("OverlayBackground", () => { styles: { height: "100px" }, }); }); + + it("updates the inline menu position property's list height value", () => { + overlayBackground["inlineMenuPosition"] = { + list: { height: 50, top: 1, left: 2, width: 3 }, + }; + + sendPortMessage(listMessageConnectorSpy, { + command: "updateAutofillInlineMenuListHeight", + styles: { height: "100px" }, + portKey, + }); + + expect(overlayBackground["inlineMenuPosition"]).toStrictEqual({ + list: { height: 100, top: 1, left: 2, width: 3 }, + }); + }); }); }); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 3b770af2004..74ec5071099 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -52,6 +52,7 @@ import { SubFrameOffsetData, SubFrameOffsetsForTab, CloseInlineMenuMessage, + InlineMenuPosition, ToggleInlineMenuHiddenMessage, } from "./abstractions/overlay.background"; @@ -67,6 +68,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private inlineMenuListPort: chrome.runtime.Port; private inlineMenuCiphers: Map = new Map(); private inlineMenuPageTranslations: Record; + private inlineMenuPosition: InlineMenuPosition = {}; private delayedCloseTimeout: number | NodeJS.Timeout; private startInlineMenuFadeInSubject = new Subject(); private cancelInlineMenuFadeInSubject = new Subject(); @@ -99,6 +101,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { focusAutofillInlineMenuList: () => this.focusInlineMenuList(), updateAutofillInlineMenuPosition: ({ message, sender }) => this.updateInlineMenuPosition(message, sender), + getAutofillInlineMenuPosition: () => this.getInlineMenuPosition(), updateAutofillInlineMenuElementIsVisibleStatus: ({ message, sender }) => this.updateInlineMenuElementIsVisibleStatus(message, sender), checkIsAutofillInlineMenuButtonVisible: () => this.checkIsInlineMenuButtonVisible(), @@ -751,6 +754,13 @@ export class OverlayBackground implements OverlayBackgroundInterface { } } + /** + * Returns the position of the currently open inline menu. + */ + private getInlineMenuPosition(): InlineMenuPosition { + return this.inlineMenuPosition; + } + /** * Handles updating the opacity of both the inline menu button and list. * This is used to simultaneously fade in the inline menu elements. @@ -807,11 +817,18 @@ export class OverlayBackground implements OverlayBackgroundInterface { ? subFrameLeftOffset + left + width - height - (fieldPaddingRight - elementOffset + 2) : subFrameLeftOffset + left + width - height + elementOffset / 2; + this.inlineMenuPosition.button = { + top: Math.round(elementTopPosition), + left: Math.round(elementLeftPosition), + height: Math.round(elementHeight), + width: Math.round(elementHeight), + }; + return { - top: `${Math.round(elementTopPosition)}px`, - left: `${Math.round(elementLeftPosition)}px`, - height: `${Math.round(elementHeight)}px`, - width: `${Math.round(elementHeight)}px`, + top: `${this.inlineMenuPosition.button.top}px`, + left: `${this.inlineMenuPosition.button.left}px`, + height: `${this.inlineMenuPosition.button.height}px`, + width: `${this.inlineMenuPosition.button.width}px`, }; } @@ -824,10 +841,18 @@ export class OverlayBackground implements OverlayBackgroundInterface { const subFrameLeftOffset = subFrameOffsets?.left || 0; const { top, left, width, height } = this.focusedFieldData.focusedFieldRects; + + this.inlineMenuPosition.list = { + top: Math.round(top + height + subFrameTopOffset), + left: Math.round(left + subFrameLeftOffset), + height: 0, + width: Math.round(width), + }; + return { - width: `${Math.round(width)}px`, - top: `${Math.round(top + height + subFrameTopOffset)}px`, - left: `${Math.round(left + subFrameLeftOffset)}px`, + width: `${this.inlineMenuPosition.list.width}px`, + top: `${this.inlineMenuPosition.list.top}px`, + left: `${this.inlineMenuPosition.list.left}px`, }; } @@ -1205,6 +1230,11 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param message - Contains the dimensions of the inline menu list */ private updateInlineMenuListHeight(message: OverlayBackgroundExtensionMessage) { + const parsedHeight = parseInt(message.styles?.height); + if (this.inlineMenuPosition.list && parsedHeight > 0) { + this.inlineMenuPosition.list.height = parsedHeight; + } + this.inlineMenuListPort?.postMessage({ command: "updateAutofillInlineMenuPosition", styles: message.styles, @@ -1532,11 +1562,13 @@ export class OverlayBackground implements OverlayBackgroundInterface { if (port.name === AutofillOverlayPort.List) { this.inlineMenuListPort = null; this.isInlineMenuListVisible = false; + this.inlineMenuPosition.list = null; } if (port.name === AutofillOverlayPort.Button) { this.inlineMenuButtonPort = null; this.isInlineMenuButtonVisible = false; + this.inlineMenuPosition.button = null; } }; } diff --git a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts index 1572770a163..616c883f188 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts @@ -11,9 +11,12 @@ describe("AutofillInlineMenuContentService", () => { let autofillInit: AutofillInit; let sendExtensionMessageSpy: jest.SpyInstance; let observeBodyMutationsSpy: jest.SpyInstance; + const waitForIdleCallback = () => + new Promise((resolve) => globalThis.requestIdleCallback(resolve)); beforeEach(() => { globalThis.document.body.innerHTML = ""; + globalThis.requestIdleCallback = jest.fn((cb, options) => setTimeout(cb, 100)); autofillInlineMenuContentService = new AutofillInlineMenuContentService(); autofillInit = new AutofillInit(null, autofillInlineMenuContentService); autofillInit.init(); @@ -302,6 +305,7 @@ describe("AutofillInlineMenuContentService", () => { autofillInlineMenuContentService["listElement"] = undefined; await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled(); }); @@ -315,12 +319,23 @@ describe("AutofillInlineMenuContentService", () => { .mockReturnValue(true); await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + await waitForIdleCallback(); + + expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled(); + }); + + it("skips re-arranging the DOM elements if the last child of the body is non-existent", async () => { + document.body.innerHTML = ""; + + await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled(); }); it("skips re-arranging the DOM elements if the last child of the body is the overlay list and the second to last child of the body is the overlay button", async () => { await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled(); }); @@ -330,6 +345,7 @@ describe("AutofillInlineMenuContentService", () => { isInlineMenuListVisibleSpy.mockResolvedValue(false); await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled(); }); @@ -339,6 +355,7 @@ describe("AutofillInlineMenuContentService", () => { document.body.insertBefore(injectedElement, listElement); await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith( buttonElement, @@ -350,6 +367,7 @@ describe("AutofillInlineMenuContentService", () => { document.body.appendChild(buttonElement); await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith( buttonElement, @@ -362,12 +380,59 @@ describe("AutofillInlineMenuContentService", () => { document.body.appendChild(injectedElement); await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + await waitForIdleCallback(); expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith( injectedElement, buttonElement, ); }); + + describe("handling an element that attempts to force itself as the last child", () => { + let persistentLastChild: HTMLElement; + + beforeEach(() => { + persistentLastChild = document.createElement("div"); + persistentLastChild.style.setProperty("z-index", "2147483647"); + document.body.appendChild(persistentLastChild); + autofillInlineMenuContentService["lastElementOverrides"].set(persistentLastChild, 3); + }); + + it("sets the z-index of to a lower value", async () => { + await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + await waitForIdleCallback(); + + expect(persistentLastChild.style.getPropertyValue("z-index")).toBe("2147483646"); + }); + + it("closes the inline menu if the persistent last child overlays the inline menu button", async () => { + sendExtensionMessageSpy.mockResolvedValue({ + button: { top: 0, left: 0, width: 0, height: 0 }, + }); + globalThis.document.elementFromPoint = jest.fn(() => persistentLastChild); + + await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + await waitForIdleCallback(); + + expect(sendExtensionMessageSpy).toHaveBeenCalledWith("autofillOverlayElementClosed", { + overlayElement: AutofillOverlayElement.Button, + }); + }); + + it("closes the inline menu if the persistent last child overlays the inline menu list", async () => { + sendExtensionMessageSpy.mockResolvedValue({ + list: { top: 0, left: 0, width: 0, height: 0 }, + }); + globalThis.document.elementFromPoint = jest.fn(() => persistentLastChild); + + await autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"](); + await waitForIdleCallback(); + + expect(sendExtensionMessageSpy).toHaveBeenCalledWith("autofillOverlayElementClosed", { + overlayElement: AutofillOverlayElement.List, + }); + }); + }); }); describe("isTriggeringExcessiveMutationObserverIterations", () => { diff --git a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts index 767445e53c2..b8702c74434 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts @@ -1,3 +1,7 @@ +import { + InlineMenuElementPosition, + InlineMenuPosition, +} from "../../../background/abstractions/overlay.background"; import { AutofillExtensionMessage } from "../../../content/abstractions/autofill-init"; import { AutofillOverlayElement, @@ -7,6 +11,7 @@ import { sendExtensionMessage, generateRandomCustomElementName, setElementStyles, + requestIdleCallbackPolyfill, } from "../../../utils"; import { InlineMenuExtensionMessageHandlers, @@ -28,6 +33,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte private bodyElementMutationObserver: MutationObserver; private mutationObserverIterations = 0; private mutationObserverIterationsResetTimeout: number | NodeJS.Timeout; + private lastElementOverrides: WeakMap = new WeakMap(); private readonly customElementDefaultStyles: Partial = { all: "initial", position: "fixed", @@ -375,12 +381,35 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte return; } + requestIdleCallbackPolyfill(this.processBodyElementMutation, { timeout: 500 }); + }; + + /** + * Processes the mutation of the body element. Will trigger when an + * idle moment in the execution of the main thread is detected. + */ + private processBodyElementMutation = async () => { const lastChild = globalThis.document.body.lastElementChild; const secondToLastChild = lastChild?.previousElementSibling; const lastChildIsInlineMenuList = lastChild === this.listElement; const lastChildIsInlineMenuButton = lastChild === this.buttonElement; const secondToLastChildIsInlineMenuButton = secondToLastChild === this.buttonElement; + if (!lastChild) { + return; + } + + const lastChildEncounterCount = this.lastElementOverrides.get(lastChild) || 0; + if (!lastChildIsInlineMenuList && !lastChildIsInlineMenuButton && lastChildEncounterCount < 3) { + this.lastElementOverrides.set(lastChild, lastChildEncounterCount + 1); + } + + if (this.lastElementOverrides.get(lastChild) >= 3) { + await this.handlePersistentLastChildOverride(lastChild); + + return; + } + if ( !lastChild || (lastChildIsInlineMenuList && secondToLastChildIsInlineMenuButton) || @@ -400,6 +429,46 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte globalThis.document.body.insertBefore(lastChild, this.buttonElement); }; + /** + * Verifies if the last child of the body element is overlaying the inline menu elements. + * This is triggered when the last child of the body is being forced by some script to + * be an element other than the inline menu elements. + * + * @param lastChild - The last child of the body element. + */ + private async handlePersistentLastChildOverride(lastChild: Element) { + const lastChildZIndex = parseInt((lastChild as HTMLElement).style.zIndex); + if (lastChildZIndex >= 2147483647) { + (lastChild as HTMLElement).style.zIndex = "2147483646"; + } + + const inlineMenuPosition: InlineMenuPosition = await this.sendExtensionMessage( + "getAutofillInlineMenuPosition", + ); + const { button, list } = inlineMenuPosition; + + if (!!button && this.elementAtCenterOfInlineMenuPosition(button) === lastChild) { + this.closeInlineMenu(); + return; + } + + if (!!list && this.elementAtCenterOfInlineMenuPosition(list) === lastChild) { + this.closeInlineMenu(); + } + } + + /** + * Returns the element present at the center of the inline menu position. + * + * @param position - The position of the inline menu element. + */ + private elementAtCenterOfInlineMenuPosition(position: InlineMenuElementPosition): Element | null { + return globalThis.document.elementFromPoint( + position.left + position.width / 2, + position.top + position.height / 2, + ); + } + /** * Identifies if the mutation observer is triggering excessive iterations. * Will trigger a blur of the most recently focused field and remove the diff --git a/apps/browser/src/autofill/popup/settings/autofill-v1.component.html b/apps/browser/src/autofill/popup/settings/autofill-v1.component.html new file mode 100644 index 00000000000..9c7047c4cb7 --- /dev/null +++ b/apps/browser/src/autofill/popup/settings/autofill-v1.component.html @@ -0,0 +1,221 @@ +
+
+ +
+

+ {{ "autofill" | i18n }} +

+
+
+
+
+
+ +
+ +
+
+
+
+ + +
+ +
+
+
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+

{{ "additionalOptions" | i18n }}

+
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
diff --git a/apps/browser/src/autofill/popup/settings/autofill-v1.component.ts b/apps/browser/src/autofill/popup/settings/autofill-v1.component.ts new file mode 100644 index 00000000000..261c6e459bf --- /dev/null +++ b/apps/browser/src/autofill/popup/settings/autofill-v1.component.ts @@ -0,0 +1,301 @@ +import { Component, OnInit } from "@angular/core"; +import { firstValueFrom } from "rxjs"; + +import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; +import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { + InlineMenuVisibilitySetting, + ClearClipboardDelaySetting, +} from "@bitwarden/common/autofill/types"; +import { + UriMatchStrategy, + UriMatchStrategySetting, +} from "@bitwarden/common/models/domain/domain-service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; +import { DialogService } from "@bitwarden/components"; + +import { BrowserApi } from "../../../platform/browser/browser-api"; +import { enableAccountSwitching } from "../../../platform/flags"; +import { AutofillService } from "../../services/abstractions/autofill.service"; + +@Component({ + selector: "app-autofill-v1", + templateUrl: "autofill-v1.component.html", +}) +export class AutofillV1Component implements OnInit { + protected canOverrideBrowserAutofillSetting = false; + protected defaultBrowserAutofillDisabled = false; + protected autoFillOverlayVisibility: InlineMenuVisibilitySetting; + protected autoFillOverlayVisibilityOptions: any[]; + protected disablePasswordManagerLink: string; + enableAutoFillOnPageLoad = false; + autoFillOnPageLoadDefault = false; + autoFillOnPageLoadOptions: any[]; + enableContextMenuItem = false; + enableAutoTotpCopy = false; // TODO: Does it matter if this is set to false or true? + clearClipboard: ClearClipboardDelaySetting; + clearClipboardOptions: any[]; + defaultUriMatch: UriMatchStrategySetting = UriMatchStrategy.Domain; + uriMatchOptions: any[]; + showCardsCurrentTab = false; + showIdentitiesCurrentTab = false; + autofillKeyboardHelperText: string; + accountSwitcherEnabled = false; + + constructor( + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, + private domainSettingsService: DomainSettingsService, + private autofillService: AutofillService, + private dialogService: DialogService, + private autofillSettingsService: AutofillSettingsServiceAbstraction, + private messagingService: MessagingService, + private vaultSettingsService: VaultSettingsService, + ) { + this.autoFillOverlayVisibilityOptions = [ + { + name: i18nService.t("autofillOverlayVisibilityOff"), + value: AutofillOverlayVisibility.Off, + }, + { + name: i18nService.t("autofillOverlayVisibilityOnFieldFocus"), + value: AutofillOverlayVisibility.OnFieldFocus, + }, + { + name: i18nService.t("autofillOverlayVisibilityOnButtonClick"), + value: AutofillOverlayVisibility.OnButtonClick, + }, + ]; + this.autoFillOnPageLoadOptions = [ + { name: i18nService.t("autoFillOnPageLoadYes"), value: true }, + { name: i18nService.t("autoFillOnPageLoadNo"), value: false }, + ]; + this.clearClipboardOptions = [ + { name: i18nService.t("never"), value: null }, + { name: i18nService.t("tenSeconds"), value: 10 }, + { name: i18nService.t("twentySeconds"), value: 20 }, + { name: i18nService.t("thirtySeconds"), value: 30 }, + { name: i18nService.t("oneMinute"), value: 60 }, + { name: i18nService.t("twoMinutes"), value: 120 }, + { name: i18nService.t("fiveMinutes"), value: 300 }, + ]; + this.uriMatchOptions = [ + { name: i18nService.t("baseDomain"), value: UriMatchStrategy.Domain }, + { name: i18nService.t("host"), value: UriMatchStrategy.Host }, + { name: i18nService.t("startsWith"), value: UriMatchStrategy.StartsWith }, + { name: i18nService.t("regEx"), value: UriMatchStrategy.RegularExpression }, + { name: i18nService.t("exact"), value: UriMatchStrategy.Exact }, + { name: i18nService.t("never"), value: UriMatchStrategy.Never }, + ]; + + this.accountSwitcherEnabled = enableAccountSwitching(); + this.disablePasswordManagerLink = this.getDisablePasswordManagerLink(); + } + + async ngOnInit() { + this.canOverrideBrowserAutofillSetting = + this.platformUtilsService.isChrome() || + this.platformUtilsService.isEdge() || + this.platformUtilsService.isOpera() || + this.platformUtilsService.isVivaldi(); + + this.defaultBrowserAutofillDisabled = await this.browserAutofillSettingCurrentlyOverridden(); + + this.autoFillOverlayVisibility = await firstValueFrom( + this.autofillSettingsService.inlineMenuVisibility$, + ); + + this.enableAutoFillOnPageLoad = await firstValueFrom( + this.autofillSettingsService.autofillOnPageLoad$, + ); + + this.autoFillOnPageLoadDefault = await firstValueFrom( + this.autofillSettingsService.autofillOnPageLoadDefault$, + ); + + this.enableContextMenuItem = await firstValueFrom( + this.autofillSettingsService.enableContextMenu$, + ); + + this.enableAutoTotpCopy = await firstValueFrom(this.autofillSettingsService.autoCopyTotp$); + + this.clearClipboard = await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$); + + const defaultUriMatch = await firstValueFrom( + this.domainSettingsService.defaultUriMatchStrategy$, + ); + this.defaultUriMatch = defaultUriMatch == null ? UriMatchStrategy.Domain : defaultUriMatch; + + const command = await this.platformUtilsService.getAutofillKeyboardShortcut(); + await this.setAutofillKeyboardHelperText(command); + + this.showCardsCurrentTab = await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$); + + this.showIdentitiesCurrentTab = await firstValueFrom( + this.vaultSettingsService.showIdentitiesCurrentTab$, + ); + } + + async updateAutoFillOverlayVisibility() { + await this.autofillSettingsService.setInlineMenuVisibility(this.autoFillOverlayVisibility); + await this.requestPrivacyPermission(); + } + + async updateAutoFillOnPageLoad() { + await this.autofillSettingsService.setAutofillOnPageLoad(this.enableAutoFillOnPageLoad); + } + + async updateAutoFillOnPageLoadDefault() { + await this.autofillSettingsService.setAutofillOnPageLoadDefault(this.autoFillOnPageLoadDefault); + } + + async saveDefaultUriMatch() { + await this.domainSettingsService.setDefaultUriMatchStrategy(this.defaultUriMatch); + } + + private async setAutofillKeyboardHelperText(command: string) { + if (command) { + this.autofillKeyboardHelperText = this.i18nService.t("autofillShortcutText", command); + } else { + this.autofillKeyboardHelperText = this.i18nService.t("autofillShortcutNotSet"); + } + } + + async commandSettings() { + if (this.platformUtilsService.isChrome()) { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + BrowserApi.createNewTab("chrome://extensions/shortcuts"); + } else if (this.platformUtilsService.isOpera()) { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + BrowserApi.createNewTab("opera://extensions/shortcuts"); + } else if (this.platformUtilsService.isEdge()) { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + BrowserApi.createNewTab("edge://extensions/shortcuts"); + } else if (this.platformUtilsService.isVivaldi()) { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + BrowserApi.createNewTab("vivaldi://extensions/shortcuts"); + } else { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + BrowserApi.createNewTab("https://bitwarden.com/help/keyboard-shortcuts"); + } + } + + private getDisablePasswordManagerLink(): string { + if (this.platformUtilsService.isChrome()) { + return "chrome://settings/autofill"; + } + if (this.platformUtilsService.isOpera()) { + return "opera://settings/autofill"; + } + if (this.platformUtilsService.isEdge()) { + return "edge://settings/passwords"; + } + if (this.platformUtilsService.isVivaldi()) { + return "vivaldi://settings/autofill"; + } + + return "https://bitwarden.com/help/disable-browser-autofill/"; + } + + protected openDisablePasswordManagerLink(event: Event) { + event.preventDefault(); + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + BrowserApi.createNewTab(this.disablePasswordManagerLink); + } + + async requestPrivacyPermission() { + if ( + this.autoFillOverlayVisibility === AutofillOverlayVisibility.Off || + !this.canOverrideBrowserAutofillSetting || + (await this.browserAutofillSettingCurrentlyOverridden()) + ) { + return; + } + + await this.dialogService.openSimpleDialog({ + title: { key: "overrideDefaultBrowserAutofillTitle" }, + content: { key: "overrideDefaultBrowserAutofillDescription" }, + acceptButtonText: { key: "makeDefault" }, + acceptAction: async () => await this.handleOverrideDialogAccept(), + cancelButtonText: { key: "ignore" }, + type: "info", + }); + } + + async updateDefaultBrowserAutofillDisabled() { + const privacyPermissionGranted = await this.privacyPermissionGranted(); + if (!this.defaultBrowserAutofillDisabled && !privacyPermissionGranted) { + return; + } + + if ( + !privacyPermissionGranted && + !(await BrowserApi.requestPermission({ permissions: ["privacy"] })) + ) { + await this.dialogService.openSimpleDialog({ + title: { key: "privacyPermissionAdditionNotGrantedTitle" }, + content: { key: "privacyPermissionAdditionNotGrantedDescription" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: null, + type: "warning", + }); + this.defaultBrowserAutofillDisabled = false; + + return; + } + + BrowserApi.updateDefaultBrowserAutofillSettings(!this.defaultBrowserAutofillDisabled); + } + + private handleOverrideDialogAccept = async () => { + this.defaultBrowserAutofillDisabled = true; + await this.updateDefaultBrowserAutofillDisabled(); + }; + + async browserAutofillSettingCurrentlyOverridden() { + if (!this.canOverrideBrowserAutofillSetting) { + return false; + } + + if (!(await this.privacyPermissionGranted())) { + return false; + } + + return await BrowserApi.browserAutofillSettingsOverridden(); + } + + async privacyPermissionGranted(): Promise { + return await BrowserApi.permissionsGranted(["privacy"]); + } + + async updateContextMenuItem() { + await this.autofillSettingsService.setEnableContextMenu(this.enableContextMenuItem); + this.messagingService.send("bgUpdateContextMenu"); + } + + async updateAutoTotpCopy() { + await this.autofillSettingsService.setAutoCopyTotp(this.enableAutoTotpCopy); + } + + async saveClearClipboard() { + await this.autofillSettingsService.setClearClipboardDelay(this.clearClipboard); + } + + async updateShowCardsCurrentTab() { + await this.vaultSettingsService.setShowCardsCurrentTab(this.showCardsCurrentTab); + } + + async updateShowIdentitiesCurrentTab() { + await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab); + } +} diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.html b/apps/browser/src/autofill/popup/settings/autofill.component.html index 30e00d4e641..5a7623f21c3 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.html +++ b/apps/browser/src/autofill/popup/settings/autofill.component.html @@ -1,221 +1,234 @@ -
-
- + + + + + + + +
+ + +

{{ "autofillSuggestionsSectionTitle" | i18n }}

+
+ + + + {{ "showInlineMenuLabel" | i18n }} + + {{ "showInlineMenuOnFormFieldsDescAlt" | i18n }} + + + + + + {{ "showInlineMenuOnIconSelectionLabel" | i18n }} + + + {{ "turnOffBrowserBuiltInPasswordManagerSettings" | i18n }} + + {{ "turnOffBrowserBuiltInPasswordManagerSettingsLink" | i18n }} + + + + + + {{ + "overrideDefaultBrowserAutoFillSettings" | i18n + }} + + {{ "turnOffBrowserBuiltInPasswordManagerSettings" | i18n }} + + {{ "turnOffBrowserBuiltInPasswordManagerSettingsLink" | i18n }} + + + + + + {{ "showCardsInVaultView" | i18n }} + + + + + {{ "showIdentitiesInVaultView" | i18n }} + + + +
+ + +

{{ "autofillKeyboardShortcutSectionTitle" | i18n }}

+
+ + + +
+ + +

{{ "enableAutoFillOnPageLoadSectionTitle" | i18n }}

+
+ + + {{ "enableAutoFillOnPageLoadDesc" | i18n }} + + + {{ "learnMoreAboutAutofillOnPageLoadLinkText" | i18n }} + + + + + {{ "enableAutoFillOnPageLoad" | i18n }} + + + {{ "defaultAutoFillOnPageLoad" | i18n }} + + + {{ "defaultAutoFillOnPageLoadDesc" | i18n }} + + + +
+ + +

{{ "additionalOptions" | i18n }}

+
+ + + + {{ "enableContextMenuItem" | i18n }} + + + + {{ "enableAutoTotpCopy" | i18n }} + + + {{ "clearClipboard" | i18n }} + + + {{ "clearClipboardDesc" | i18n }} + + + + {{ "defaultUriMatchDetection" | i18n }} + + + {{ "defaultUriMatchDetectionDesc" | i18n }} + + + +
-

- {{ "autofill" | i18n }} -

-
-
-
-
-
- -
- -
-
-
-
- - -
- -
-
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-

{{ "additionalOptions" | i18n }}

-
-
- - -
-
- -
-
- - -
-
- -
-
- - -
-
- -
-
- - -
-
- -
-
- - -
-
- -
-
- - -
-
- -
-
+ diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index 7b8a1c32b44..44dcb04a139 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -1,12 +1,25 @@ +import { CommonModule } from "@angular/common"; import { Component, OnInit } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; import { firstValueFrom } from "rxjs"; -import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + AutofillOverlayVisibility, + BrowserClientVendors, + BrowserShortcutsUris, + ClearClipboardDelay, + DisablePasswordManagerUris, +} from "@bitwarden/common/autofill/constants"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { - InlineMenuVisibilitySetting, + BrowserClientVendor, + BrowserShortcutsUri, ClearClipboardDelaySetting, + DisablePasswordManagerUri, + InlineMenuVisibilitySetting, } from "@bitwarden/common/autofill/types"; import { UriMatchStrategy, @@ -16,33 +29,77 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; -import { DialogService } from "@bitwarden/components"; +import { + CardComponent, + CheckboxModule, + DialogService, + FormFieldModule, + IconButtonModule, + ItemModule, + LinkModule, + SectionComponent, + SectionHeaderComponent, + SelectModule, + TypographyModule, +} from "@bitwarden/components"; import { BrowserApi } from "../../../platform/browser/browser-api"; -import { enableAccountSwitching } from "../../../platform/flags"; -import { AutofillService } from "../../services/abstractions/autofill.service"; +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @Component({ - selector: "app-autofill", templateUrl: "autofill.component.html", + standalone: true, + imports: [ + CardComponent, + CheckboxModule, + CommonModule, + FormFieldModule, + FormsModule, + IconButtonModule, + ItemModule, + JslibModule, + LinkModule, + PopOutComponent, + PopupFooterComponent, + PopupHeaderComponent, + PopupPageComponent, + RouterModule, + SectionComponent, + SectionHeaderComponent, + SelectModule, + TypographyModule, + ], }) export class AutofillComponent implements OnInit { + /* + * Default values set here are used in component state operations + * until corresponding stored settings have loaded on init. + */ protected canOverrideBrowserAutofillSetting = false; protected defaultBrowserAutofillDisabled = false; - protected autoFillOverlayVisibility: InlineMenuVisibilitySetting; - protected autoFillOverlayVisibilityOptions: any[]; - protected disablePasswordManagerLink: string; - enableAutoFillOnPageLoad = false; - autoFillOnPageLoadDefault = false; - autoFillOnPageLoadOptions: any[]; + protected inlineMenuVisibility: InlineMenuVisibilitySetting = + AutofillOverlayVisibility.OnFieldFocus; + protected browserClientVendor: BrowserClientVendor = BrowserClientVendors.Unknown; + protected disablePasswordManagerURI: DisablePasswordManagerUri = + DisablePasswordManagerUris.Unknown; + protected browserShortcutsURI: BrowserShortcutsUri = BrowserShortcutsUris.Unknown; + protected browserClientIsUnknown: boolean; + enableAutofillOnPageLoad = false; + enableInlineMenu = false; + enableInlineMenuOnIconSelect = false; + autofillOnPageLoadDefault = false; + autofillOnPageLoadOptions: { name: string; value: boolean }[]; enableContextMenuItem = false; - enableAutoTotpCopy = false; // TODO: Does it matter if this is set to false or true? + enableAutoTotpCopy = false; clearClipboard: ClearClipboardDelaySetting; - clearClipboardOptions: any[]; + clearClipboardOptions: { name: string; value: ClearClipboardDelaySetting }[]; defaultUriMatch: UriMatchStrategySetting = UriMatchStrategy.Domain; - uriMatchOptions: any[]; - showCardsCurrentTab = false; - showIdentitiesCurrentTab = false; + uriMatchOptions: { name: string; value: UriMatchStrategySetting }[]; + showCardsCurrentTab = true; + showIdentitiesCurrentTab = true; autofillKeyboardHelperText: string; accountSwitcherEnabled = false; @@ -50,38 +107,23 @@ export class AutofillComponent implements OnInit { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private domainSettingsService: DomainSettingsService, - private autofillService: AutofillService, private dialogService: DialogService, private autofillSettingsService: AutofillSettingsServiceAbstraction, private messagingService: MessagingService, private vaultSettingsService: VaultSettingsService, ) { - this.autoFillOverlayVisibilityOptions = [ - { - name: i18nService.t("autofillOverlayVisibilityOff"), - value: AutofillOverlayVisibility.Off, - }, - { - name: i18nService.t("autofillOverlayVisibilityOnFieldFocus"), - value: AutofillOverlayVisibility.OnFieldFocus, - }, - { - name: i18nService.t("autofillOverlayVisibilityOnButtonClick"), - value: AutofillOverlayVisibility.OnButtonClick, - }, - ]; - this.autoFillOnPageLoadOptions = [ + this.autofillOnPageLoadOptions = [ { name: i18nService.t("autoFillOnPageLoadYes"), value: true }, { name: i18nService.t("autoFillOnPageLoadNo"), value: false }, ]; this.clearClipboardOptions = [ - { name: i18nService.t("never"), value: null }, - { name: i18nService.t("tenSeconds"), value: 10 }, - { name: i18nService.t("twentySeconds"), value: 20 }, - { name: i18nService.t("thirtySeconds"), value: 30 }, - { name: i18nService.t("oneMinute"), value: 60 }, - { name: i18nService.t("twoMinutes"), value: 120 }, - { name: i18nService.t("fiveMinutes"), value: 300 }, + { name: i18nService.t("never"), value: ClearClipboardDelay.Never }, + { name: i18nService.t("tenSeconds"), value: ClearClipboardDelay.TenSeconds }, + { name: i18nService.t("twentySeconds"), value: ClearClipboardDelay.TwentySeconds }, + { name: i18nService.t("thirtySeconds"), value: ClearClipboardDelay.ThirtySeconds }, + { name: i18nService.t("oneMinute"), value: ClearClipboardDelay.OneMinute }, + { name: i18nService.t("twoMinutes"), value: ClearClipboardDelay.TwoMinutes }, + { name: i18nService.t("fiveMinutes"), value: ClearClipboardDelay.FiveMinutes }, ]; this.uriMatchOptions = [ { name: i18nService.t("baseDomain"), value: UriMatchStrategy.Domain }, @@ -92,28 +134,32 @@ export class AutofillComponent implements OnInit { { name: i18nService.t("never"), value: UriMatchStrategy.Never }, ]; - this.accountSwitcherEnabled = enableAccountSwitching(); - this.disablePasswordManagerLink = this.getDisablePasswordManagerLink(); + this.browserClientVendor = this.getBrowserClientVendor(); + this.disablePasswordManagerURI = DisablePasswordManagerUris[this.browserClientVendor]; + this.browserShortcutsURI = BrowserShortcutsUris[this.browserClientVendor]; + this.browserClientIsUnknown = this.browserClientVendor === BrowserClientVendors.Unknown; } async ngOnInit() { - this.canOverrideBrowserAutofillSetting = - this.platformUtilsService.isChrome() || - this.platformUtilsService.isEdge() || - this.platformUtilsService.isOpera() || - this.platformUtilsService.isVivaldi(); - + this.canOverrideBrowserAutofillSetting = !this.browserClientIsUnknown; this.defaultBrowserAutofillDisabled = await this.browserAutofillSettingCurrentlyOverridden(); - this.autoFillOverlayVisibility = await firstValueFrom( + this.inlineMenuVisibility = await firstValueFrom( this.autofillSettingsService.inlineMenuVisibility$, ); - this.enableAutoFillOnPageLoad = await firstValueFrom( + this.enableInlineMenuOnIconSelect = + this.inlineMenuVisibility === AutofillOverlayVisibility.OnButtonClick; + + this.enableInlineMenu = + this.inlineMenuVisibility === AutofillOverlayVisibility.OnFieldFocus || + this.enableInlineMenuOnIconSelect; + + this.enableAutofillOnPageLoad = await firstValueFrom( this.autofillSettingsService.autofillOnPageLoad$, ); - this.autoFillOnPageLoadDefault = await firstValueFrom( + this.autofillOnPageLoadDefault = await firstValueFrom( this.autofillSettingsService.autofillOnPageLoadDefault$, ); @@ -140,17 +186,27 @@ export class AutofillComponent implements OnInit { ); } - async updateAutoFillOverlayVisibility() { - await this.autofillSettingsService.setInlineMenuVisibility(this.autoFillOverlayVisibility); + async updateInlineMenuVisibility() { + if (!this.enableInlineMenu) { + this.enableInlineMenuOnIconSelect = false; + } + + const newInlineMenuVisibilityValue = this.enableInlineMenuOnIconSelect + ? AutofillOverlayVisibility.OnButtonClick + : this.enableInlineMenu + ? AutofillOverlayVisibility.OnFieldFocus + : AutofillOverlayVisibility.Off; + + await this.autofillSettingsService.setInlineMenuVisibility(newInlineMenuVisibilityValue); await this.requestPrivacyPermission(); } - async updateAutoFillOnPageLoad() { - await this.autofillSettingsService.setAutofillOnPageLoad(this.enableAutoFillOnPageLoad); + async updateAutofillOnPageLoad() { + await this.autofillSettingsService.setAutofillOnPageLoad(this.enableAutofillOnPageLoad); } - async updateAutoFillOnPageLoadDefault() { - await this.autofillSettingsService.setAutofillOnPageLoadDefault(this.autoFillOnPageLoadDefault); + async updateAutofillOnPageLoadDefault() { + await this.autofillSettingsService.setAutofillOnPageLoadDefault(this.autofillOnPageLoadDefault); } async saveDefaultUriMatch() { @@ -165,57 +221,81 @@ export class AutofillComponent implements OnInit { } } - async commandSettings() { + private getBrowserClientVendor(): BrowserClientVendor { if (this.platformUtilsService.isChrome()) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - BrowserApi.createNewTab("chrome://extensions/shortcuts"); - } else if (this.platformUtilsService.isOpera()) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - BrowserApi.createNewTab("opera://extensions/shortcuts"); - } else if (this.platformUtilsService.isEdge()) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - BrowserApi.createNewTab("edge://extensions/shortcuts"); - } else if (this.platformUtilsService.isVivaldi()) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - BrowserApi.createNewTab("vivaldi://extensions/shortcuts"); - } else { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - BrowserApi.createNewTab("https://bitwarden.com/help/keyboard-shortcuts"); + return BrowserClientVendors.Chrome; } - } - private getDisablePasswordManagerLink(): string { - if (this.platformUtilsService.isChrome()) { - return "chrome://settings/autofill"; - } if (this.platformUtilsService.isOpera()) { - return "opera://settings/autofill"; - } - if (this.platformUtilsService.isEdge()) { - return "edge://settings/passwords"; - } - if (this.platformUtilsService.isVivaldi()) { - return "vivaldi://settings/autofill"; + return BrowserClientVendors.Opera; } - return "https://bitwarden.com/help/disable-browser-autofill/"; + if (this.platformUtilsService.isEdge()) { + return BrowserClientVendors.Edge; + } + + if (this.platformUtilsService.isVivaldi()) { + return BrowserClientVendors.Vivaldi; + } + + return BrowserClientVendors.Unknown; } - protected openDisablePasswordManagerLink(event: Event) { + protected async openURI(event: Event, uri: BrowserShortcutsUri | DisablePasswordManagerUri) { event.preventDefault(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - BrowserApi.createNewTab(this.disablePasswordManagerLink); + + // If the destination is a password management settings page, ask the user to confirm before proceeding + if (uri === DisablePasswordManagerUris[this.browserClientVendor]) { + await this.dialogService.openSimpleDialog({ + ...(this.browserClientIsUnknown + ? { + content: { key: "confirmContinueToHelpCenterPasswordManagementContent" }, + title: { key: "confirmContinueToHelpCenter" }, + } + : { + content: { key: "confirmContinueToBrowserPasswordManagementSettingsContent" }, + title: { key: "confirmContinueToBrowserSettingsTitle" }, + }), + acceptButtonText: { key: "continue" }, + acceptAction: async () => { + await BrowserApi.createNewTab(uri); + }, + cancelButtonText: { key: "cancel" }, + type: "info", + }); + + return; + } + + // If the destination is a browser shortcut settings page, ask the user to confirm before proceeding + if (uri === BrowserShortcutsUris[this.browserClientVendor]) { + await this.dialogService.openSimpleDialog({ + ...(this.browserClientIsUnknown + ? { + content: { key: "confirmContinueToHelpCenterKeyboardShortcutsContent" }, + title: { key: "confirmContinueToHelpCenter" }, + } + : { + content: { key: "confirmContinueToBrowserKeyboardShortcutSettingsContent" }, + title: { key: "confirmContinueToBrowserSettingsTitle" }, + }), + acceptButtonText: { key: "continue" }, + acceptAction: async () => { + await BrowserApi.createNewTab(uri); + }, + cancelButtonText: { key: "cancel" }, + type: "info", + }); + + return; + } + + await BrowserApi.createNewTab(uri); } async requestPrivacyPermission() { if ( - this.autoFillOverlayVisibility === AutofillOverlayVisibility.Off || + this.inlineMenuVisibility === AutofillOverlayVisibility.Off || !this.canOverrideBrowserAutofillSetting || (await this.browserAutofillSettingCurrentlyOverridden()) ) { @@ -225,9 +305,9 @@ export class AutofillComponent implements OnInit { await this.dialogService.openSimpleDialog({ title: { key: "overrideDefaultBrowserAutofillTitle" }, content: { key: "overrideDefaultBrowserAutofillDescription" }, - acceptButtonText: { key: "makeDefault" }, + acceptButtonText: { key: "continue" }, acceptAction: async () => await this.handleOverrideDialogAccept(), - cancelButtonText: { key: "ignore" }, + cancelButtonText: { key: "cancel" }, type: "info", }); } diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.html b/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.html new file mode 100644 index 00000000000..8f78ac16404 --- /dev/null +++ b/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.html @@ -0,0 +1,91 @@ +
+
+
+ +
+

+ {{ "excludedDomains" | i18n }} +

+
+ +
+
+
+
+
+ + +
+ +
+ + + + +
+
+ +
+
+
+ +
+
+
+
diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.ts b/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.ts new file mode 100644 index 00000000000..362ac4896c2 --- /dev/null +++ b/apps/browser/src/autofill/popup/settings/excluded-domains-v1.component.ts @@ -0,0 +1,141 @@ +import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; +import { Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; + +import { BrowserApi } from "../../../platform/browser/browser-api"; +import { enableAccountSwitching } from "../../../platform/flags"; + +interface ExcludedDomain { + uri: string; + showCurrentUris: boolean; +} + +const BroadcasterSubscriptionId = "excludedDomains"; + +@Component({ + selector: "app-excluded-domains-v1", + templateUrl: "excluded-domains-v1.component.html", +}) +export class ExcludedDomainsV1Component implements OnInit, OnDestroy { + excludedDomains: ExcludedDomain[] = []; + existingExcludedDomains: ExcludedDomain[] = []; + currentUris: string[]; + loadCurrentUrisTimeout: number; + accountSwitcherEnabled = false; + + constructor( + private domainSettingsService: DomainSettingsService, + private i18nService: I18nService, + private router: Router, + private broadcasterService: BroadcasterService, + private ngZone: NgZone, + private platformUtilsService: PlatformUtilsService, + ) { + this.accountSwitcherEnabled = enableAccountSwitching(); + } + + async ngOnInit() { + const savedDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); + if (savedDomains) { + for (const uri of Object.keys(savedDomains)) { + this.excludedDomains.push({ uri: uri, showCurrentUris: false }); + this.existingExcludedDomains.push({ uri: uri, showCurrentUris: false }); + } + } + + await this.loadCurrentUris(); + + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.ngZone.run(async () => { + switch (message.command) { + case "tabChanged": + case "windowChanged": + if (this.loadCurrentUrisTimeout != null) { + window.clearTimeout(this.loadCurrentUrisTimeout); + } + this.loadCurrentUrisTimeout = window.setTimeout( + async () => await this.loadCurrentUris(), + 500, + ); + break; + default: + break; + } + }); + }); + } + + ngOnDestroy() { + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + } + + async addUri() { + this.excludedDomains.push({ uri: "", showCurrentUris: false }); + } + + async removeUri(i: number) { + this.excludedDomains.splice(i, 1); + } + + async submit() { + const savedDomains: { [name: string]: null } = {}; + const newExcludedDomains = this.getNewlyAddedDomains(this.excludedDomains); + for (const domain of this.excludedDomains) { + const resp = newExcludedDomains.filter((e) => e.uri === domain.uri); + if (resp.length === 0) { + savedDomains[domain.uri] = null; + } else { + if (domain.uri && domain.uri !== "") { + const validDomain = Utils.getHostname(domain.uri); + if (!validDomain) { + this.platformUtilsService.showToast( + "error", + null, + this.i18nService.t("excludedDomainsInvalidDomain", domain.uri), + ); + return; + } + savedDomains[validDomain] = null; + } + } + } + + await this.domainSettingsService.setNeverDomains(savedDomains); + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.router.navigate(["/tabs/settings"]); + } + + trackByFunction(index: number, item: any) { + return index; + } + + getNewlyAddedDomains(domain: ExcludedDomain[]): ExcludedDomain[] { + const result = this.excludedDomains.filter( + (newDomain) => + !this.existingExcludedDomains.some((oldDomain) => newDomain.uri === oldDomain.uri), + ); + return result; + } + + toggleUriInput(domain: ExcludedDomain) { + domain.showCurrentUris = !domain.showCurrentUris; + } + + async loadCurrentUris() { + const tabs = await BrowserApi.tabsQuery({ windowType: "normal" }); + if (tabs) { + const uriSet = new Set(tabs.map((tab) => Utils.getHostname(tab.url))); + uriSet.delete(null); + this.currentUris = Array.from(uriSet); + } + } +} diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains.component.html b/apps/browser/src/autofill/popup/settings/excluded-domains.component.html index 8f78ac16404..8f909a336b8 100644 --- a/apps/browser/src/autofill/popup/settings/excluded-domains.component.html +++ b/apps/browser/src/autofill/popup/settings/excluded-domains.component.html @@ -1,91 +1,63 @@ -
-
-
- -
-

- {{ "excludedDomains" | i18n }} -

-
- -
-
-
-
-
- - -
- -
- - - - -
-
- -
-
-
- -
-
-
-
+ + {{ + "websiteItemLabel" | i18n: i + 1 + }} + +
{{ domain }}
+
+ + + + + + + + + + diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts index 5dad991dfa4..76e913a3831 100644 --- a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts +++ b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts @@ -1,141 +1,166 @@ -import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; +import { CommonModule } from "@angular/common"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { Router, RouterModule } from "@angular/router"; import { firstValueFrom } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { + ButtonModule, + CardComponent, + FormFieldModule, + IconButtonModule, + ItemModule, + LinkModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, +} from "@bitwarden/components"; -import { BrowserApi } from "../../../platform/browser/browser-api"; import { enableAccountSwitching } from "../../../platform/flags"; +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; -interface ExcludedDomain { - uri: string; - showCurrentUris: boolean; -} - -const BroadcasterSubscriptionId = "excludedDomains"; +const BroadcasterSubscriptionId = "excludedDomainsState"; @Component({ selector: "app-excluded-domains", templateUrl: "excluded-domains.component.html", + standalone: true, + imports: [ + ButtonModule, + CardComponent, + CommonModule, + FormFieldModule, + FormsModule, + IconButtonModule, + ItemModule, + JslibModule, + LinkModule, + PopOutComponent, + PopupFooterComponent, + PopupHeaderComponent, + PopupPageComponent, + RouterModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, + ], }) export class ExcludedDomainsComponent implements OnInit, OnDestroy { - excludedDomains: ExcludedDomain[] = []; - existingExcludedDomains: ExcludedDomain[] = []; - currentUris: string[]; - loadCurrentUrisTimeout: number; accountSwitcherEnabled = false; + dataIsPristine = true; + excludedDomainsState: string[] = []; + storedExcludedDomains: string[] = []; + // How many fields should be non-editable before editable fields + fieldsEditThreshold: number = 0; constructor( private domainSettingsService: DomainSettingsService, private i18nService: I18nService, private router: Router, private broadcasterService: BroadcasterService, - private ngZone: NgZone, private platformUtilsService: PlatformUtilsService, ) { this.accountSwitcherEnabled = enableAccountSwitching(); } async ngOnInit() { - const savedDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); - if (savedDomains) { - for (const uri of Object.keys(savedDomains)) { - this.excludedDomains.push({ uri: uri, showCurrentUris: false }); - this.existingExcludedDomains.push({ uri: uri, showCurrentUris: false }); - } + const neverDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); + + if (neverDomains) { + this.storedExcludedDomains = Object.keys(neverDomains); } - await this.loadCurrentUris(); + this.excludedDomainsState = [...this.storedExcludedDomains]; - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.ngZone.run(async () => { - switch (message.command) { - case "tabChanged": - case "windowChanged": - if (this.loadCurrentUrisTimeout != null) { - window.clearTimeout(this.loadCurrentUrisTimeout); - } - this.loadCurrentUrisTimeout = window.setTimeout( - async () => await this.loadCurrentUris(), - 500, - ); - break; - default: - break; - } - }); - }); + // Do not allow the first x (pre-existing) fields to be edited + this.fieldsEditThreshold = this.storedExcludedDomains.length; } ngOnDestroy() { this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); } - async addUri() { - this.excludedDomains.push({ uri: "", showCurrentUris: false }); + async addNewDomain() { + // add empty field to the Domains list interface + this.excludedDomainsState.push(""); + + await this.fieldChange(); } - async removeUri(i: number) { - this.excludedDomains.splice(i, 1); + async removeDomain(i: number) { + this.excludedDomainsState.splice(i, 1); + + // if a pre-existing field was dropped, lower the edit threshold + if (i < this.fieldsEditThreshold) { + this.fieldsEditThreshold--; + } + + await this.fieldChange(); } - async submit() { - const savedDomains: { [name: string]: null } = {}; - const newExcludedDomains = this.getNewlyAddedDomains(this.excludedDomains); - for (const domain of this.excludedDomains) { - const resp = newExcludedDomains.filter((e) => e.uri === domain.uri); - if (resp.length === 0) { - savedDomains[domain.uri] = null; - } else { - if (domain.uri && domain.uri !== "") { - const validDomain = Utils.getHostname(domain.uri); - if (!validDomain) { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("excludedDomainsInvalidDomain", domain.uri), - ); - return; - } - savedDomains[validDomain] = null; + async fieldChange() { + if (this.dataIsPristine) { + this.dataIsPristine = false; + } + } + + async saveChanges() { + if (this.dataIsPristine) { + await this.router.navigate(["/notifications"]); + + return; + } + + const newExcludedDomainsSaveState: NeverDomains = {}; + const uniqueExcludedDomains = new Set(this.excludedDomainsState); + + for (const uri of uniqueExcludedDomains) { + if (uri && uri !== "") { + const validatedHost = Utils.getHostname(uri); + + if (!validatedHost) { + this.platformUtilsService.showToast( + "error", + null, + this.i18nService.t("excludedDomainsInvalidDomain", uri), + ); + + return; } + + newExcludedDomainsSaveState[validatedHost] = null; } } - await this.domainSettingsService.setNeverDomains(savedDomains); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/tabs/settings"]); + try { + await this.domainSettingsService.setNeverDomains(newExcludedDomainsSaveState); + + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("excludedDomainsSavedSuccess"), + ); + } catch { + this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError")); + + // Do not navigate on error + return; + } + + await this.router.navigate(["/notifications"]); } - trackByFunction(index: number, item: any) { + trackByFunction(index: number, _: string) { return index; } - - getNewlyAddedDomains(domain: ExcludedDomain[]): ExcludedDomain[] { - const result = this.excludedDomains.filter( - (newDomain) => - !this.existingExcludedDomains.some((oldDomain) => newDomain.uri === oldDomain.uri), - ); - return result; - } - - toggleUriInput(domain: ExcludedDomain) { - domain.showCurrentUris = !domain.showCurrentUris; - } - - async loadCurrentUris() { - const tabs = await BrowserApi.tabsQuery({ windowType: "normal" }); - if (tabs) { - const uriSet = new Set(tabs.map((tab) => Utils.getHostname(tab.url))); - uriSet.delete(null); - this.currentUris = Array.from(uriSet); - } - } } diff --git a/apps/browser/src/autofill/popup/settings/notifications-v1.component.html b/apps/browser/src/autofill/popup/settings/notifications-v1.component.html new file mode 100644 index 00000000000..89d83c9e480 --- /dev/null +++ b/apps/browser/src/autofill/popup/settings/notifications-v1.component.html @@ -0,0 +1,89 @@ +
+
+ +
+

+ {{ "notifications" | i18n }} +

+
+ +
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+
+ +
+
+
diff --git a/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts b/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts new file mode 100644 index 00000000000..442e91e55eb --- /dev/null +++ b/apps/browser/src/autofill/popup/settings/notifications-v1.component.ts @@ -0,0 +1,53 @@ +import { Component, OnInit } from "@angular/core"; +import { firstValueFrom } from "rxjs"; + +import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service"; +import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; + +import { enableAccountSwitching } from "../../../platform/flags"; + +@Component({ + selector: "autofill-notification-v1-settings", + templateUrl: "notifications-v1.component.html", +}) +export class NotificationsSettingsV1Component implements OnInit { + enableAddLoginNotification = false; + enableChangedPasswordNotification = false; + enablePasskeys = true; + accountSwitcherEnabled = false; + + constructor( + private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction, + private vaultSettingsService: VaultSettingsService, + ) { + this.accountSwitcherEnabled = enableAccountSwitching(); + } + + async ngOnInit() { + this.enableAddLoginNotification = await firstValueFrom( + this.userNotificationSettingsService.enableAddedLoginPrompt$, + ); + + this.enableChangedPasswordNotification = await firstValueFrom( + this.userNotificationSettingsService.enableChangedPasswordPrompt$, + ); + + this.enablePasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$); + } + + async updateAddLoginNotification() { + await this.userNotificationSettingsService.setEnableAddedLoginPrompt( + this.enableAddLoginNotification, + ); + } + + async updateChangedPasswordNotification() { + await this.userNotificationSettingsService.setEnableChangedPasswordPrompt( + this.enableChangedPasswordNotification, + ); + } + + async updateEnablePasskeys() { + await this.vaultSettingsService.setEnablePasskeys(this.enablePasskeys); + } +} diff --git a/apps/browser/src/autofill/popup/settings/notifications.component.html b/apps/browser/src/autofill/popup/settings/notifications.component.html index 89d83c9e480..d39b61ce10c 100644 --- a/apps/browser/src/autofill/popup/settings/notifications.component.html +++ b/apps/browser/src/autofill/popup/settings/notifications.component.html @@ -1,89 +1,56 @@ -
-
- + + + + + + + +
+ + +

{{ "vaultSaveOptionsTitle" | i18n }}

+
+ +
+ + +
+
+ + +
+
+ + +
+
+
+ + + {{ "excludedDomains" | i18n }} + + +
-

- {{ "notifications" | i18n }} -

-
- -
-
-
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
- -
-
-
+ diff --git a/apps/browser/src/autofill/popup/settings/notifications.component.ts b/apps/browser/src/autofill/popup/settings/notifications.component.ts index f4a7773916e..be447e3f885 100644 --- a/apps/browser/src/autofill/popup/settings/notifications.component.ts +++ b/apps/browser/src/autofill/popup/settings/notifications.component.ts @@ -1,27 +1,57 @@ +import { CommonModule } from "@angular/common"; import { Component, OnInit } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; import { firstValueFrom } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; +import { + ItemModule, + CardComponent, + SectionComponent, + SectionHeaderComponent, + CheckboxModule, + TypographyModule, + FormFieldModule, +} from "@bitwarden/components"; -import { enableAccountSwitching } from "../../../platform/flags"; +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @Component({ - selector: "autofill-notification-settings", templateUrl: "notifications.component.html", + standalone: true, + imports: [ + CommonModule, + JslibModule, + RouterModule, + PopupPageComponent, + PopupHeaderComponent, + PopupFooterComponent, + PopOutComponent, + ItemModule, + CardComponent, + SectionComponent, + SectionHeaderComponent, + CheckboxModule, + TypographyModule, + FormFieldModule, + FormsModule, + ], }) export class NotificationsSettingsComponent implements OnInit { enableAddLoginNotification = false; enableChangedPasswordNotification = false; enablePasskeys = true; - accountSwitcherEnabled = false; constructor( private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction, private vaultSettingsService: VaultSettingsService, - ) { - this.accountSwitcherEnabled = enableAccountSwitching(); - } + ) {} async ngOnInit() { this.enableAddLoginNotification = await firstValueFrom( diff --git a/apps/browser/src/autofill/services/abstractions/dom-element-visibility.service.ts b/apps/browser/src/autofill/services/abstractions/dom-element-visibility.service.ts index b7fd958bfd1..b5d52b8c418 100644 --- a/apps/browser/src/autofill/services/abstractions/dom-element-visibility.service.ts +++ b/apps/browser/src/autofill/services/abstractions/dom-element-visibility.service.ts @@ -1,6 +1,4 @@ -interface DomElementVisibilityService { +export interface DomElementVisibilityService { isFormFieldViewable: (element: HTMLElement) => Promise; isElementHiddenByCss: (element: HTMLElement) => boolean; } - -export { DomElementVisibilityService }; diff --git a/apps/browser/src/autofill/services/dom-element-visibility.service.ts b/apps/browser/src/autofill/services/dom-element-visibility.service.ts index 67986eb00f2..9df4ccb8fbc 100644 --- a/apps/browser/src/autofill/services/dom-element-visibility.service.ts +++ b/apps/browser/src/autofill/services/dom-element-visibility.service.ts @@ -1,9 +1,9 @@ import { AutofillInlineMenuContentService } from "../overlay/inline-menu/abstractions/autofill-inline-menu-content.service"; import { FillableFormFieldElement, FormFieldElement } from "../types"; -import { DomElementVisibilityService as domElementVisibilityServiceInterface } from "./abstractions/dom-element-visibility.service"; +import { DomElementVisibilityService as DomElementVisibilityServiceInterface } from "./abstractions/dom-element-visibility.service"; -class DomElementVisibilityService implements domElementVisibilityServiceInterface { +class DomElementVisibilityService implements DomElementVisibilityServiceInterface { private cachedComputedStyle: CSSStyleDeclaration | null = null; constructor(private inlineMenuElements?: AutofillInlineMenuContentService) {} diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index c271dd29db3..86400e4d173 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -697,6 +697,7 @@ export default class MainBackground { this.secureStorageService, this.userDecryptionOptionsService, this.logService, + this.configService, ); this.devicesService = new DevicesServiceImplementation(this.devicesApiService); @@ -1026,8 +1027,6 @@ export default class MainBackground { this.accountService, ); this.nativeMessagingBackground = new NativeMessagingBackground( - this.accountService, - this.masterPasswordService, this.cryptoService, this.cryptoFunctionService, this.runtimeBackground, @@ -1477,16 +1476,7 @@ export default class MainBackground { return; } - const storage = await this.storageService.getAll(); - await this.storageService.clear(); - - for (const key in storage) { - // eslint-disable-next-line - if (!storage.hasOwnProperty(key)) { - continue; - } - await this.storageService.save(key, storage[key]); - } + await this.storageService.reseed(); } async clearClipboard(clipboardValue: string, clearMs: number) { diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index 534a239a811..3fb943f613b 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -1,8 +1,6 @@ import { firstValueFrom } from "rxjs"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; @@ -15,7 +13,7 @@ import { BiometricStateService } from "@bitwarden/common/platform/biometrics/bio import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { UserKey, MasterKey } from "@bitwarden/common/types/key"; +import { UserKey } from "@bitwarden/common/types/key"; import { BrowserApi } from "../platform/browser/browser-api"; @@ -73,8 +71,6 @@ export class NativeMessagingBackground { private validatingFingerprint: boolean; constructor( - private accountService: AccountService, - private masterPasswordService: InternalMasterPasswordServiceAbstraction, private cryptoService: CryptoService, private cryptoFunctionService: CryptoFunctionService, private runtimeBackground: RuntimeBackground, @@ -355,27 +351,6 @@ export class NativeMessagingBackground { Utils.fromB64ToArray(message.userKeyB64), ) as UserKey; await this.cryptoService.setUserKey(userKey); - } else if (message.keyB64) { - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - // Backwards compatibility to support cases in which the user hasn't updated their desktop app - // TODO: Remove after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3472) - const encUserKeyPrim = await this.stateService.getEncryptedCryptoSymmetricKey(); - const encUserKey = - encUserKeyPrim != null - ? new EncString(encUserKeyPrim) - : await this.masterPasswordService.getMasterKeyEncryptedUserKey(userId); - if (!encUserKey) { - throw new Error("No encrypted user key found"); - } - const masterKey = new SymmetricCryptoKey( - Utils.fromB64ToArray(message.keyB64), - ) as MasterKey; - const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey( - masterKey, - encUserKey, - ); - await this.masterPasswordService.setMasterKey(masterKey, userId); - await this.cryptoService.setUserKey(userKey); } else { throw new Error("No key received"); } diff --git a/apps/browser/src/platform/popup/layout/popup-layout.mdx b/apps/browser/src/platform/popup/layout/popup-layout.mdx index b805805ad18..49f76501aea 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.mdx +++ b/apps/browser/src/platform/popup/layout/popup-layout.mdx @@ -41,6 +41,9 @@ page looks nice when the extension is popped out. - `footer` - Use the `popup-footer` component. - Not every page will have a footer. +- `above-scroll-area` + - When the page content overflows, this content will be "stuck" to the top of the page upon + scrolling. - default - Whatever content you want in `main`. diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts index f2208a8b8f5..408988dca3b 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -12,6 +12,8 @@ import { IconButtonModule, ItemModule, NoItemsModule, + SearchModule, + SectionComponent, } from "@bitwarden/components"; import { PopupFooterComponent } from "./popup-footer.component"; @@ -33,30 +35,36 @@ class ExtensionContainerComponent {} @Component({ selector: "vault-placeholder", template: ` - - - + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + `, standalone: true, - imports: [CommonModule, ItemModule, BadgeModule, IconButtonModule], + imports: [CommonModule, ItemModule, BadgeModule, IconButtonModule, SectionComponent], }) class VaultComponent { protected data = Array.from(Array(20).keys()); @@ -103,6 +111,18 @@ class MockPopoutButtonComponent {} }) class MockCurrentAccountComponent {} +@Component({ + selector: "mock-search", + template: ` +
+ +
+ `, + standalone: true, + imports: [SearchModule], +}) +class MockSearchComponent {} + @Component({ selector: "mock-vault-page", template: ` @@ -114,6 +134,7 @@ class MockCurrentAccountComponent {} + `, @@ -124,6 +145,7 @@ class MockCurrentAccountComponent {} MockAddButtonComponent, MockPopoutButtonComponent, MockCurrentAccountComponent, + MockSearchComponent, VaultComponent, ], }) diff --git a/apps/browser/src/platform/popup/layout/popup-page.component.html b/apps/browser/src/platform/popup/layout/popup-page.component.html index 87f91e781a7..d53ef24803d 100644 --- a/apps/browser/src/platform/popup/layout/popup-page.component.html +++ b/apps/browser/src/platform/popup/layout/popup-page.component.html @@ -1,10 +1,25 @@ -
+
+
+ +
- + + + + +
+
+
+ +
+ +
{ if (obj == null) { return null; @@ -22,7 +31,7 @@ export const objToStore = (obj: any) => { } return { - [serializationIndicator]: true, + [serializationIndicator]: true as const, value: JSON.stringify(obj), }; }; @@ -105,7 +114,7 @@ export default abstract class AbstractChromeStorageService } /** Backwards compatible resolution of retrieved object with new serialized storage */ - protected processGetObject(obj: T | serializedObject): T | null { + protected processGetObject(obj: T | SerializedValue): T | null { if (this.isSerialized(obj)) { obj = JSON.parse(obj.value); } @@ -113,8 +122,8 @@ export default abstract class AbstractChromeStorageService } /** Type guard for whether an object is tagged as serialized */ - protected isSerialized(value: T | serializedObject): value is serializedObject { - const asSerialized = value as serializedObject; + protected isSerialized(value: T | SerializedValue): value is SerializedValue { + const asSerialized = value as SerializedValue; return ( asSerialized != null && asSerialized[serializationIndicator] && diff --git a/apps/browser/src/platform/services/browser-local-storage.service.spec.ts b/apps/browser/src/platform/services/browser-local-storage.service.spec.ts index 37ea37dbf6f..bd79dd6fa56 100644 --- a/apps/browser/src/platform/services/browser-local-storage.service.spec.ts +++ b/apps/browser/src/platform/services/browser-local-storage.service.spec.ts @@ -1,89 +1,173 @@ import { objToStore } from "./abstractions/abstract-chrome-storage-api.service"; -import BrowserLocalStorageService from "./browser-local-storage.service"; +import BrowserLocalStorageService, { + RESEED_IN_PROGRESS_KEY, +} from "./browser-local-storage.service"; + +const apiGetLike = + (store: Record) => (key: string, callback: (items: { [key: string]: any }) => void) => { + if (key == null) { + callback(store); + } else { + callback({ [key]: store[key] }); + } + }; describe("BrowserLocalStorageService", () => { let service: BrowserLocalStorageService; let store: Record; + let changeListener: (changes: { [key: string]: chrome.storage.StorageChange }) => void; + + let saveMock: jest.Mock; + let getMock: jest.Mock; + let clearMock: jest.Mock; + let removeMock: jest.Mock; beforeEach(() => { store = {}; - service = new BrowserLocalStorageService(); - }); - - describe("clear", () => { - let clearMock: jest.Mock; - - beforeEach(() => { - clearMock = chrome.storage.local.clear as jest.Mock; + // Record change listener + chrome.storage.local.onChanged.addListener = jest.fn((listener) => { + changeListener = listener; }); - it("uses the api to clear", async () => { - await service.clear(); + service = new BrowserLocalStorageService(); + + // setup mocks + getMock = chrome.storage.local.get as jest.Mock; + getMock.mockImplementation(apiGetLike(store)); + saveMock = chrome.storage.local.set as jest.Mock; + saveMock.mockImplementation((update, callback) => { + Object.entries(update).forEach(([key, value]) => { + store[key] = value; + }); + callback(); + }); + clearMock = chrome.storage.local.clear as jest.Mock; + clearMock.mockImplementation((callback) => { + store = {}; + callback?.(); + }); + removeMock = chrome.storage.local.remove as jest.Mock; + removeMock.mockImplementation((keys, callback) => { + if (Array.isArray(keys)) { + keys.forEach((key) => { + delete store[key]; + }); + } else { + delete store[keys]; + } + + callback(); + }); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("reseed", () => { + it.each([ + { + key1: objToStore("value1"), + key2: objToStore("value2"), + key3: null, + }, + {}, + ])("saves all data in storage %s", async (testStore) => { + for (const key of Object.keys(testStore) as Array) { + store[key] = testStore[key]; + } + await service.reseed(); + + expect(saveMock).toHaveBeenLastCalledWith( + { ...testStore, [RESEED_IN_PROGRESS_KEY]: objToStore(true) }, + expect.any(Function), + ); + }); + + it.each([ + { + key1: objToStore("value1"), + key2: objToStore("value2"), + key3: null, + }, + {}, + ])("results in the same store %s", async (testStore) => { + for (const key of Object.keys(testStore) as Array) { + store[key] = testStore[key]; + } + await service.reseed(); + + expect(store).toEqual(testStore); + }); + + it("converts non-serialized values to serialized", async () => { + store.key1 = "value1"; + store.key2 = "value2"; + + const expectedStore = { + key1: objToStore("value1"), + key2: objToStore("value2"), + reseedInProgress: objToStore(true), + }; + + await service.reseed(); + + expect(saveMock).toHaveBeenLastCalledWith(expectedStore, expect.any(Function)); + }); + + it("clears data", async () => { + await service.reseed(); expect(clearMock).toHaveBeenCalledTimes(1); }); }); - describe("getAll", () => { - let getMock: jest.Mock; + describe.each(["get", "has", "save", "remove"] as const)("%s", (method) => { + let interval: string | number | NodeJS.Timeout; - beforeEach(() => { - // setup get - getMock = chrome.storage.local.get as jest.Mock; - getMock.mockImplementation((key, callback) => { - if (key == null) { - callback(store); - } else { - callback({ [key]: store[key] }); - } - }); + afterEach(() => { + if (interval) { + clearInterval(interval); + } }); - it("returns all values", async () => { - store["key1"] = "string"; - store["key2"] = 0; - const result = await service.getAll(); + function startReseed() { + store[RESEED_IN_PROGRESS_KEY] = objToStore(true); + } - expect(result).toEqual(store); + function endReseed() { + delete store[RESEED_IN_PROGRESS_KEY]; + changeListener({ reseedInProgress: { oldValue: true } }); + } + + it("waits for reseed prior to operation", async () => { + startReseed(); + + const promise = service[method]("key", "value"); // note "value" is only used in save, but ignored in other methods + + await expect(promise).not.toBeFulfilled(10); + + endReseed(); + + await expect(promise).toBeResolved(); }); - it("handles empty stores", async () => { - const result = await service.getAll(); - - expect(result).toEqual({}); + it("does not wait if reseed is not in progress", async () => { + const promise = service[method]("key", "value"); + await expect(promise).toBeResolved(1); }); - it("handles stores with null values", async () => { - store["key"] = null; + it("awaits prior reseed operations before starting a new one", async () => { + startReseed(); - const result = await service.getAll(); - expect(result).toEqual(store); - }); + const promise = service.reseed(); - it("handles values processed for storage", async () => { - const obj = { test: 2 }; - const key = "key"; - store[key] = objToStore(obj); + await expect(promise).not.toBeFulfilled(10); - const result = await service.getAll(); + endReseed(); - expect(result).toEqual({ - [key]: obj, - }); - }); - - // This is a test of backwards compatibility before local storage was serialized. - it("handles values that were stored without processing for storage", async () => { - const obj = { test: 2 }; - const key = "key"; - store[key] = obj; - - const result = await service.getAll(); - - expect(result).toEqual({ - [key]: obj, - }); + await expect(promise).toBeResolved(); }); }); }); diff --git a/apps/browser/src/platform/services/browser-local-storage.service.ts b/apps/browser/src/platform/services/browser-local-storage.service.ts index e1f9f63676f..7957b6edeaf 100644 --- a/apps/browser/src/platform/services/browser-local-storage.service.ts +++ b/apps/browser/src/platform/services/browser-local-storage.service.ts @@ -1,15 +1,91 @@ -import AbstractChromeStorageService from "./abstractions/abstract-chrome-storage-api.service"; +import { defer, filter, firstValueFrom, map, merge, throwError, timeout } from "rxjs"; + +import AbstractChromeStorageService, { + SerializedValue, + objToStore, +} from "./abstractions/abstract-chrome-storage-api.service"; + +export const RESEED_IN_PROGRESS_KEY = "reseedInProgress"; export default class BrowserLocalStorageService extends AbstractChromeStorageService { constructor() { super(chrome.storage.local); + this.chromeStorageApi.remove(RESEED_IN_PROGRESS_KEY, () => { + return; + }); + } + + /** + * Reads, clears, and re-saves all data in local storage. This is a hack to remove previously stored sensitive data from + * local storage logs. + * + * @see https://github.com/bitwarden/clients/issues/485 + */ + async reseed(): Promise { + try { + await this.save(RESEED_IN_PROGRESS_KEY, true); + const data = await this.getAll(); + await this.clear(); + await this.saveAll(data); + } finally { + await super.remove(RESEED_IN_PROGRESS_KEY); + } + } + + override async get(key: string): Promise { + await this.awaitReseed(); + return super.get(key); + } + + override async has(key: string): Promise { + await this.awaitReseed(); + return super.has(key); + } + + override async save(key: string, obj: any): Promise { + await this.awaitReseed(); + return super.save(key, obj); + } + + override async remove(key: string): Promise { + await this.awaitReseed(); + return super.remove(key); + } + + private async awaitReseed(): Promise { + const notReseeding = async () => { + return !(await super.get(RESEED_IN_PROGRESS_KEY)); + }; + + const finishedReseeding = this.updates$.pipe( + filter(({ key, updateType }) => key === RESEED_IN_PROGRESS_KEY && updateType === "remove"), + map(() => true), + ); + + await firstValueFrom( + merge(defer(notReseeding), finishedReseeding).pipe( + filter((v) => v), + timeout({ + // We eventually need to give up and throw an error + first: 5_000, + with: () => + throwError( + () => new Error("Reseeding local storage did not complete in a timely manner."), + ), + }), + ), + ); } /** * Clears local storage */ - async clear() { - await chrome.storage.local.clear(); + private async clear() { + return new Promise((resolve) => { + this.chromeStorageApi.clear(() => { + resolve(); + }); + }); } /** @@ -18,7 +94,7 @@ export default class BrowserLocalStorageService extends AbstractChromeStorageSer * @remarks This method processes values prior to resolving, do not use `chrome.storage.local` directly * @returns Promise resolving to keyed object of all stored data */ - async getAll(): Promise> { + private async getAll(): Promise> { return new Promise((resolve) => { this.chromeStorageApi.get(null, (allStorage) => { const resolved = Object.entries(allStorage).reduce( @@ -32,4 +108,19 @@ export default class BrowserLocalStorageService extends AbstractChromeStorageSer }); }); } + + private async saveAll(data: Record): Promise { + return new Promise((resolve) => { + const keyedData = Object.entries(data).reduce( + (agg, [key, value]) => { + agg[key] = objToStore(value); + return agg; + }, + {} as Record, + ); + this.chromeStorageApi.set(keyedData, () => { + resolve(); + }); + }); + } } diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 53ab778c31d..bb9d0383785 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -38,8 +38,11 @@ import { TwoFactorAuthComponent } from "../auth/popup/two-factor-auth.component" import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component"; import { TwoFactorComponent } from "../auth/popup/two-factor.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; +import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; +import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component"; import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; +import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; import { PremiumComponent } from "../billing/popup/settings/premium.component"; import BrowserPopupUtils from "../platform/popup/browser-popup-utils"; @@ -48,7 +51,7 @@ import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/pass import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.component"; import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component"; import { SendTypeComponent } from "../tools/popup/send/send-type.component"; -import { SendV2Component } from "../tools/popup/send/send-v2.component"; +import { SendV2Component } from "../tools/popup/send-v2/send-v2.component"; import { AboutPageV2Component } from "../tools/popup/settings/about-page/about-page-v2.component"; import { AboutPageComponent } from "../tools/popup/settings/about-page/about-page.component"; import { MoreFromBitwardenPageV2Component } from "../tools/popup/settings/about-page/more-from-bitwarden-page-v2.component"; @@ -276,24 +279,23 @@ const routes: Routes = [ canActivate: [AuthGuard], data: { state: "export" }, }), - { + ...extensionRefreshSwap(AutofillV1Component, AutofillComponent, { path: "autofill", - component: AutofillComponent, canActivate: [AuthGuard], data: { state: "autofill" }, - }, + }), { path: "account-security", component: AccountSecurityComponent, canActivate: [AuthGuard], data: { state: "account-security" }, }, - { + ...extensionRefreshSwap(NotificationsSettingsV1Component, NotificationsSettingsComponent, { path: "notifications", - component: NotificationsSettingsComponent, + component: NotificationsSettingsV1Component, canActivate: [AuthGuard], data: { state: "notifications" }, - }, + }), ...extensionRefreshSwap(VaultSettingsComponent, VaultSettingsV2Component, { path: "vault-settings", canActivate: [AuthGuard], @@ -323,12 +325,12 @@ const routes: Routes = [ canActivate: [AuthGuard], data: { state: "sync" }, }, - { + ...extensionRefreshSwap(ExcludedDomainsV1Component, ExcludedDomainsComponent, { path: "excluded-domains", - component: ExcludedDomainsComponent, + component: ExcludedDomainsV1Component, canActivate: [AuthGuard], data: { state: "excluded-domains" }, - }, + }), { path: "premium", component: PremiumComponent, diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 3c7f45e55f7..d3caef579cb 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -36,8 +36,11 @@ import { SsoComponent } from "../auth/popup/sso.component"; import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component"; import { TwoFactorComponent } from "../auth/popup/two-factor.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; +import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; +import { ExcludedDomainsV1Component } from "../autofill/popup/settings/excluded-domains-v1.component"; import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; +import { NotificationsSettingsV1Component } from "../autofill/popup/settings/notifications-v1.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; import { PremiumComponent } from "../billing/popup/settings/premium.component"; import { PopOutComponent } from "../platform/popup/components/pop-out.component"; @@ -91,6 +94,7 @@ import "../platform/popup/locales"; imports: [ A11yModule, AppRoutingModule, + AutofillComponent, ToastModule.forRoot({ maxOpened: 2, autoDismiss: true, @@ -108,10 +112,12 @@ import "../platform/popup/locales"; ScrollingModule, ServicesModule, DialogModule, + ExcludedDomainsComponent, FilePopoutCalloutComponent, AvatarModule, AccountComponent, ButtonModule, + NotificationsSettingsComponent, PopOutComponent, PopupPageComponent, PopupTabNavigationComponent, @@ -133,7 +139,7 @@ import "../platform/popup/locales"; ColorPasswordCountPipe, CurrentTabComponent, EnvironmentComponent, - ExcludedDomainsComponent, + ExcludedDomainsV1Component, Fido2CipherRowComponent, Fido2UseBrowserLinkComponent, FolderAddEditComponent, @@ -146,7 +152,7 @@ import "../platform/popup/locales"; LoginComponent, LoginViaAuthRequestComponent, LoginDecryptionOptionsComponent, - NotificationsSettingsComponent, + NotificationsSettingsV1Component, AppearanceComponent, GeneratorComponent, PasswordGeneratorHistoryComponent, @@ -176,7 +182,7 @@ import "../platform/popup/locales"; RemovePasswordComponent, VaultSelectComponent, Fido2Component, - AutofillComponent, + AutofillV1Component, EnvironmentSelectorComponent, AccountSwitcherComponent, ], diff --git a/apps/browser/src/safari/safari/SafariWebExtensionHandler.swift b/apps/browser/src/safari/safari/SafariWebExtensionHandler.swift index 95369453409..b0688e3bebb 100644 --- a/apps/browser/src/safari/safari/SafariWebExtensionHandler.swift +++ b/apps/browser/src/safari/safari/SafariWebExtensionHandler.swift @@ -133,12 +133,6 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(fallbackName.utf8.count), fallbackName, &passwordLength, &passwordPtr, nil) } - // TODO: Remove after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3473) - if status != errSecSuccess { - let secondaryFallbackName = "_masterkey_biometric" - status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(secondaryFallbackName.utf8.count), secondaryFallbackName, &passwordLength, &passwordPtr, nil) - } - if status == errSecSuccess { let result = NSString(bytes: passwordPtr!, length: Int(passwordLength), encoding: String.Encoding.utf8.rawValue) as String? SecKeychainItemFreeContent(nil, passwordPtr) diff --git a/apps/browser/src/tools/popup/send/send-v2.component.html b/apps/browser/src/tools/popup/send-v2/send-v2.component.html similarity index 100% rename from apps/browser/src/tools/popup/send/send-v2.component.html rename to apps/browser/src/tools/popup/send-v2/send-v2.component.html diff --git a/apps/browser/src/tools/popup/send/send-v2.component.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts similarity index 91% rename from apps/browser/src/tools/popup/send/send-v2.component.ts rename to apps/browser/src/tools/popup/send-v2/send-v2.component.ts index fba14b762b1..ebb014e4fcf 100644 --- a/apps/browser/src/tools/popup/send/send-v2.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts @@ -5,7 +5,11 @@ import { RouterLink } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { ButtonModule, NoItemsModule } from "@bitwarden/components"; -import { NoSendsIcon, NewSendDropdownComponent } from "@bitwarden/send-ui"; +import { + NoSendsIcon, + NewSendDropdownComponent, + SendListFiltersComponent, +} from "@bitwarden/send-ui"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; @@ -30,6 +34,7 @@ enum SendsListState { ButtonModule, RouterLink, NewSendDropdownComponent, + SendListFiltersComponent, ], }) export class SendV2Component implements OnInit, OnDestroy { diff --git a/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts b/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts index 7dd0c50003a..9c69d76228f 100644 --- a/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts +++ b/apps/browser/src/vault/popup/components/fido2/fido2-use-browser-link.component.ts @@ -4,6 +4,7 @@ import { Component } from "@angular/core"; import { firstValueFrom } from "rxjs"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -92,7 +93,7 @@ export class Fido2UseBrowserLinkComponent { const exisitingDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); const validDomain = Utils.getHostname(uri); - const savedDomains: { [name: string]: unknown } = { + const savedDomains: NeverDomains = { ...exisitingDomains, }; savedDomains[validDomain] = null; diff --git a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.html b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.html index 8e8ce1f997c..b0e651e8e2b 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.html @@ -1,5 +1,5 @@ - + diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts index 342042c95f3..a6258eaede1 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts @@ -12,12 +12,12 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { ButtonComponent } from "@bitwarden/components"; +import { CipherAttachmentsComponent } from "@bitwarden/vault"; import { PopupFooterComponent } from "../../../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup-header.component"; import { AttachmentsV2Component } from "./attachments-v2.component"; -import { CipherAttachmentsComponent } from "./cipher-attachments/cipher-attachments.component"; @Component({ standalone: true, diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts index 20e553ca748..8e6b09bceda 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts @@ -8,14 +8,13 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { ButtonModule } from "@bitwarden/components"; +import { CipherAttachmentsComponent } from "@bitwarden/vault"; import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component"; import { PopupFooterComponent } from "../../../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component"; -import { CipherAttachmentsComponent } from "./cipher-attachments/cipher-attachments.component"; - @Component({ standalone: true, selector: "app-attachments-v2", diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html index 0a0f44e8e00..cd2e849f95b 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html @@ -34,7 +34,7 @@ type="button" bitMenuItem > - {{ "assignCollections" | i18n }} + {{ "assignToCollections" | i18n }} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html index 32427ac2da6..0e241a81dcb 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html @@ -1,5 +1,5 @@
-
+ +

{{ title }} @@ -27,9 +27,10 @@ {{ cipher.name }} {{ cipher.subTitle }} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts index b6ba09fb315..8e3f2bca26d 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts @@ -13,6 +13,7 @@ import { SectionHeaderComponent, TypographyModule, } from "@bitwarden/components"; +import { OrgIconDirective } from "@bitwarden/vault"; import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; import { PopupCipherView } from "../../../views/popup-cipher.view"; @@ -33,6 +34,7 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options RouterLink, ItemCopyActionsComponent, ItemMoreOptionsComponent, + OrgIconDirective, ], selector: "app-vault-list-items-container", templateUrl: "vault-list-items-container.component.html", @@ -76,6 +78,13 @@ export class VaultListItemsContainerComponent { @Input({ transform: booleanAttribute }) showAutofillButton: boolean; + /** + * Remove the bottom margin from the bit-section in this component + * (used for containers at the end of the page where bottom margin is not needed) + */ + @Input({ transform: booleanAttribute }) + disableSectionMargin: boolean = false; + /** * The tooltip text for the organization icon for ciphers that belong to an organization. * @param cipher diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html index c2516fe05f7..00a9d3f9489 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html @@ -4,9 +4,9 @@ - +

- + +
- +
+ +
diff --git a/apps/browser/src/vault/popup/views/popup-cipher.view.ts b/apps/browser/src/vault/popup/views/popup-cipher.view.ts index 5bb1905c59a..de371ca65d5 100644 --- a/apps/browser/src/vault/popup/views/popup-cipher.view.ts +++ b/apps/browser/src/vault/popup/views/popup-cipher.view.ts @@ -1,5 +1,4 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { ProductTierType } from "@bitwarden/common/billing/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; @@ -21,21 +20,4 @@ export class PopupCipherView extends CipherView { this.collections = collections; this.organization = organization; } - - /** - * Get the bwi icon for the cipher according to the organization type. - */ - get orgIcon(): "bwi-family" | "bwi-business" | null { - switch (this.organization?.productTierType) { - case ProductTierType.Free: - case ProductTierType.Families: - return "bwi-family"; - case ProductTierType.Teams: - case ProductTierType.Enterprise: - case ProductTierType.TeamsStarter: - return "bwi-business"; - default: - return null; - } - } } diff --git a/apps/browser/store/locales/el/copy.resx b/apps/browser/store/locales/el/copy.resx index fb50f95bdc2..f5138c7543f 100644 --- a/apps/browser/store/locales/el/copy.resx +++ b/apps/browser/store/locales/el/copy.resx @@ -118,61 +118,60 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden Password Manager + Bitwarden Διαχειριστής Κωδικών Πρόσβασης - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Στο σπίτι, στην εργασία, ή εν κινήσει, το Bitwarden ασφαλίζει εύκολα όλους τους κωδικούς πρόσβασης, τα κλειδιά πρόσβασης και τις ευαίσθητες πληροφορίες. - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + Αναγνωρίστηκε ως ο καλύτερος διαχειριστής κωδικών πρόσβασης από τα PCMag, WIRED, The Verge, CNET, G2 και άλλα! -SECURE YOUR DIGITAL LIFE -Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. +ΑΣΦΑΛΙΣΤΕ ΤΗΝ ΨΗΦΙΑΚΗ ΣΑΣ ΖΩΗ +Ασφαλίστε την ψηφιακή σας ζωή και προστατέψτε την από παραβιάσεις δεδομένων δημιουργώντας και αποθηκεύοντας μοναδικούς, ισχυρούς κωδικούς πρόσβασης για κάθε λογαριασμό. Διατηρήστε τα πάντα σε ένα κρυπτογραφημένο από άκρη σε άκρη θησαυροφυλάκιο κωδικών πρόσβασης στο οποίο μόνο εσείς έχετε πρόσβαση. -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +ΠΡΟΣΒΑΣΗ ΣΤΑ ΔΕΔΟΜΕΝΑ ΣΑΣ, ΟΠΟΥΔΗΠΟΤΕ, ΟΠΟΤΕΔΗΠΟΤΕ, ΣΕ ΟΠΟΙΑΔΗΠΟΤΕ ΣΥΣΚΕΥΗ +Διαχειριστείτε, αποθηκεύστε, ασφαλίστε και μοιραστείτε εύκολα απεριόριστους κωδικούς πρόσβασης σε απεριόριστες συσκευές χωρίς περιορισμούς. -EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE -Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. +ΟΛΟΙ ΠΡΕΠΕΙ ΝΑ ΕΧΟΥΝ ΤΑ ΕΡΓΑΛΕΙΑ ΓΙΑ ΝΑ ΠΑΡΑΜΕΝΟΥΝ ΑΣΦΑΛΕΙΣ ΣΤΟ ΔΙΑΔΙΚΤΥΟ +Χρησιμοποιήστε το Bitwarden δωρεάν χωρίς διαφημίσεις ή πώληση δεδομένων. Το Bitwarden πιστεύει ότι όλοι πρέπει να έχουν τη δυνατότητα να παραμένουν ασφαλείς στο διαδίκτυο. Τα Premium πακέτα προσφέρουν πρόσβαση σε προηγμένες δυνατότητες. -EMPOWER YOUR TEAMS WITH BITWARDEN -Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. +ΕΝΔΥΝΑΜΩΣΤΕ ΤΙΣ ΟΜΑΔΕΣ ΣΑΣ ΜΕ ΤΟ BITWARDEN +Τα πακέτα για ομάδες και επιχειρήσεις διαθέτουν επαγγελματικές επιχειρηματικές δυνατότητες. Ορισμένα παραδείγματα περιλαμβάνουν ενσωμάτωση SSO, αυτο-εξυπηρέτηση, ενσωμάτωση καταλόγου και παροχή SCIM, παγκόσμιες πολιτικές, πρόσβαση στο API, αρχεία καταγραφής συμβάντων και πολλά άλλα. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +Χρησιμοποιήστε το Bitwarden για να ασφαλίσετε το εργατικό σας δυναμικό και να μοιραστείτε ευαίσθητες πληροφορίες με τους συναδέλφους σας. +Περισσότεροι λόγοι για να επιλέξετε το Bitwarden: -More reasons to choose Bitwarden: +Κρυπτογράφηση Παγκόσμιας Κλάσης +Οι κωδικοί πρόσβασης προστατεύονται με προηγμένη κρυπτογράφηση από άκρο σε άκρο (AES-256 bit, salted hashtag και PBKDF2 SHA-256), ώστε τα δεδομένα σας να παραμένουν ασφαλή και ιδιωτικά. -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. +Έλεγχοι από Τρίτους +Το Bitwarden διεξάγει τακτικά ολοκληρωμένους ελέγχους ασφαλείας από τρίτους με αξιόλογες εταιρείες ασφαλείας. Αυτοί οι ετήσιοι έλεγχοι περιλαμβάνουν αξιολογήσεις πηγαίου κώδικα και δοκιμές διείσδυσης σε όλες τις IP του Bitwarden, τους διακομιστές και τις εφαρμογές ιστού. -3rd-party Audits -Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. - -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +Προηγμένο 2FA +Ασφαλίστε τη σύνδεσή σας με εφαρμογή αυθεντικοποίησης, κωδικούς που αποστέλλονται μέσω ηλεκτρονικού ταχυδρομείου ή διαπιστευτήρια FIDO2 WebAuthn, όπως ένα φυσικό κλειδί ασφαλείας ή ένα κλειδί πρόσβασης. Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +Μεταδώστε δεδομένα απευθείας σε άλλους, διατηρώντας κρυπτογραφημένη ασφάλεια από άκρο σε άκρο και περιορίζοντας την έκθεση. -Built-in Generator -Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. +Ενσωματωμένη γεννήτρια +Δημιουργήστε μεγάλους, σύνθετους και ξεχωριστούς κωδικούς πρόσβασης και μοναδικά ονόματα χρήστη για κάθε ιστότοπο που επισκέπτεστε. Ενσωματώστε παρόχους ψευδώνυμων για τη διεύθυνση ηλεκτρονικού ταχυδρομείου για επιπλέον προστασία της ιδιωτικής σας ζωής. -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +Παγκόσμιες μεταφράσεις +Υπάρχουν μεταφράσεις του Bitwarden για περισσότερες από 60 γλώσσες, μεταφρασμένες από την παγκόσμια κοινότητα μέσω του Crowdin. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Εφαρμογές Πολλαπλών Πλατφορμών +Ασφαλίστε και μοιραστείτε ευαίσθητα δεδομένα μέσα στο Θησαυροφυλάκιο Bitwarden από οποιοδήποτε περιηγητή, κινητή συσκευή ή λειτουργικό σύστημα και πολλά άλλα. -Bitwarden secures more than just passwords -End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! +Το Bitwarden διασφαλίζει πολλά περισσότερα από κωδικούς πρόσβασης +Οι λύσεις διαχείρισης κρυπτογραφημένων διαπιστευτηρίων από άκρη σε άκρη του Bitwarden δίνουν τη δυνατότητα στους οργανισμούς να ασφαλίζουν τα πάντα, συμπεριλαμβανομένων των μυστικών των προγραμματιστών και των κλειδιών πρόσβασης. Επισκεφθείτε το Bitwarden.com για να μάθετε περισσότερα για το Bitwarden Secrets Manager και το Bitwarden Passwordless.dev! - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Στο σπίτι, στην εργασία, ή εν κινήσει, το Bitwarden ασφαλίζει εύκολα όλους τους κωδικούς πρόσβασης, τα κλειδιά πρόσβασης και τις ευαίσθητες πληροφορίες. - Συγχρονίστε και αποκτήστε πρόσβαση στο θησαυροφυλάκιό σας από πολλαπλές συσκευές + Συγχρονίστε και αποκτήστε πρόσβαση στο θησαυ/κιό σας από πολλαπλές συσκευές Διαχειριστείτε όλες τις συνδέσεις και τους κωδικούς σας με ασφάλεια diff --git a/apps/browser/test.setup.ts b/apps/browser/test.setup.ts index 2c358b62c4e..7f10b4075f5 100644 --- a/apps/browser/test.setup.ts +++ b/apps/browser/test.setup.ts @@ -1,4 +1,7 @@ import "jest-preset-angular/setup-jest"; +import { addCustomMatchers } from "@bitwarden/common/spec"; + +addCustomMatchers(); // Add chrome storage api const QUOTA_BYTES = 10; @@ -10,6 +13,10 @@ const storage = { QUOTA_BYTES, getBytesInUse: jest.fn(), clear: jest.fn(), + onChanged: { + addListener: jest.fn(), + removeListener: jest.fn(), + }, }, session: { set: jest.fn(), diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index beb4a732125..b44d994f4e7 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -43,6 +43,7 @@ "include": [ "src", "../../libs/common/src/platform/services/**/*.worker.ts", - "../../libs/common/src/autofill/constants" + "../../libs/common/src/autofill/constants", + "../../libs/common/custom-matchers.d.ts" ] } diff --git a/apps/cli/package.json b/apps/cli/package.json index 2ae40a15ae4..47f181040c8 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -80,7 +80,7 @@ "papaparse": "5.4.1", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "tldts": "6.1.29", + "tldts": "6.1.34", "zxcvbn": "4.4.2" } } diff --git a/apps/cli/src/service-container.ts b/apps/cli/src/service-container.ts index cfe310318fb..cc533027328 100644 --- a/apps/cli/src/service-container.ts +++ b/apps/cli/src/service-container.ts @@ -534,6 +534,7 @@ export class ServiceContainer { this.secureStorageService, this.userDecryptionOptionsService, this.logService, + this.configService, ); this.authRequestService = new AuthRequestService( diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 6b1d52919ae..0ca295f9b92 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -12,6 +12,7 @@ "app": "build" }, "afterSign": "scripts/after-sign.js", + "afterPack": "scripts/after-pack.js", "asarUnpack": ["**/*.node"], "files": [ "**/*", @@ -24,7 +25,7 @@ "**/node_modules/argon2/package.json", "**/node_modules/argon2/build/Release/argon2.node" ], - "electronVersion": "31.2.0", + "electronVersion": "31.2.1", "generateUpdatesFilesForAllChannels": true, "publish": { "provider": "generic", diff --git a/apps/desktop/resources/memory-dump-wrapper.sh b/apps/desktop/resources/memory-dump-wrapper.sh new file mode 100644 index 00000000000..fa71cd73764 --- /dev/null +++ b/apps/desktop/resources/memory-dump-wrapper.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# disable core dumps +ulimit -c 0 + +APP_PATH=$(dirname "$0") +# pass through all args +$APP_PATH/bitwarden-app "$@" \ No newline at end of file diff --git a/apps/desktop/scripts/after-pack.js b/apps/desktop/scripts/after-pack.js new file mode 100644 index 00000000000..e128397e615 --- /dev/null +++ b/apps/desktop/scripts/after-pack.js @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +require("dotenv").config(); +const path = require("path"); + +const fse = require("fs-extra"); + +exports.default = run; + +async function run(context) { + console.log("## After pack"); + console.log(context); + if (context.electronPlatformName === "linux") { + console.log("Creating memory-protection wrapper script"); + const appOutDir = context.appOutDir; + const oldBin = path.join(appOutDir, context.packager.executableName); + const newBin = path.join(appOutDir, "bitwarden-app"); + fse.moveSync(oldBin, newBin); + console.log("Moved binary to bitwarden-app"); + + const wrapperScript = path.join(__dirname, "../resources/memory-dump-wrapper.sh"); + const wrapperBin = path.join(appOutDir, context.packager.executableName); + fse.copyFileSync(wrapperScript, wrapperBin); + fse.chmodSync(wrapperBin, "755"); + console.log("Copied memory-protection wrapper script"); + } +} diff --git a/apps/desktop/src/auth/two-factor-auth-duo.component.ts b/apps/desktop/src/auth/two-factor-auth-duo.component.ts new file mode 100644 index 00000000000..804afccee45 --- /dev/null +++ b/apps/desktop/src/auth/two-factor-auth-duo.component.ts @@ -0,0 +1,110 @@ +import { DialogModule } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, NgZone } from "@angular/core"; +import { ReactiveFormsModule, FormsModule } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; +import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { + AsyncActionsModule, + ButtonModule, + FormFieldModule, + LinkModule, + ToastService, + TypographyModule, +} from "@bitwarden/components"; + +import { TwoFactorAuthDuoComponent as TwoFactorAuthDuoBaseComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component"; + +const BroadcasterSubscriptionId = "TwoFactorComponent"; + +@Component({ + standalone: true, + selector: "app-two-factor-auth-duo", + templateUrl: + "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.html", + imports: [ + CommonModule, + JslibModule, + DialogModule, + ButtonModule, + LinkModule, + TypographyModule, + ReactiveFormsModule, + FormFieldModule, + AsyncActionsModule, + FormsModule, + ], + providers: [I18nPipe], +}) +export class TwoFactorAuthDuoComponent extends TwoFactorAuthDuoBaseComponent { + constructor( + protected i18nService: I18nService, + protected platformUtilsService: PlatformUtilsService, + private broadcasterService: BroadcasterService, + private ngZone: NgZone, + private environmentService: EnvironmentService, + toastService: ToastService, + ) { + super(i18nService, platformUtilsService, toastService); + } + + async ngOnInit(): Promise { + await super.ngOnInit(); + } + + duoCallbackSubscriptionEnabled: boolean = false; + + protected override setupDuoResultListener() { + if (!this.duoCallbackSubscriptionEnabled) { + this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { + await this.ngZone.run(async () => { + if (message.command === "duoCallback") { + this.token.emit(message.code + "|" + message.state); + } + }); + }); + this.duoCallbackSubscriptionEnabled = true; + } + } + + override async launchDuoFrameless() { + if (this.duoFramelessUrl === null) { + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"), + }); + return; + } + const duoHandOffMessage = { + title: this.i18nService.t("youSuccessfullyLoggedIn"), + message: this.i18nService.t("youMayCloseThisWindow"), + isCountdown: false, + }; + + // we're using the connector here as a way to set a cookie with translations + // before continuing to the duo frameless url + const env = await firstValueFrom(this.environmentService.environment$); + const launchUrl = + env.getWebVaultUrl() + + "/duo-redirect-connector.html" + + "?duoFramelessUrl=" + + encodeURIComponent(this.duoFramelessUrl) + + "&handOffMessage=" + + encodeURIComponent(JSON.stringify(duoHandOffMessage)); + this.platformUtilsService.launchUri(launchUrl); + } + + async ngOnDestroy() { + if (this.duoCallbackSubscriptionEnabled) { + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + this.duoCallbackSubscriptionEnabled = false; + } + } +} diff --git a/apps/desktop/src/auth/two-factor-auth.component.ts b/apps/desktop/src/auth/two-factor-auth.component.ts index bb1ef601388..29271b565c1 100644 --- a/apps/desktop/src/auth/two-factor-auth.component.ts +++ b/apps/desktop/src/auth/two-factor-auth.component.ts @@ -19,6 +19,8 @@ import { LinkModule } from "../../../../libs/components/src/link"; import { I18nPipe } from "../../../../libs/components/src/shared/i18n.pipe"; import { TypographyModule } from "../../../../libs/components/src/typography"; +import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; + @Component({ standalone: true, templateUrl: @@ -40,6 +42,7 @@ import { TypographyModule } from "../../../../libs/components/src/typography"; TwoFactorAuthEmailComponent, TwoFactorAuthAuthenticatorComponent, TwoFactorAuthYubikeyComponent, + TwoFactorAuthDuoComponent, TwoFactorAuthWebAuthnComponent, ], providers: [I18nPipe], diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 84a9913c287..999ec6d0dcd 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Bevestigingskode word vereis." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Ongeldige bevestigingskode" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 5885f86f253..5e7a4bb99d9 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "رمز التحقق مطلوب." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "رمز التحقق غير صالح" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "قم بتشغيل دوو واتبع الخطوات لإنهاء تسجيل الدخول." }, diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index b9dfef03424..2c0bfbaf914 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Doğrulama kodu tələb olunur." }, + "webauthnCancelOrTimeout": { + "message": "Kimlik doğrulama ləğv edildi və ya çox uzun çəkdi. Lütfən yenidən sınayın." + }, "invalidVerificationCode": { "message": "Yararsız doğrulama kodu" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Duo xidmətinə bağlanarkən xəta baş verdi. Fərqli iki addımlı giriş üsulu istifadə edin və ya kömək üçün Duo ilə əlaqə saxlayın." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Duo-nu başladın və giriş prosesini tamamlamaq üçün addımları izləyin." }, diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index e729d9ca0a2..d22164ccd34 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Патрабуецца праверачны код." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Памылковы праверачны код" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 41ff64598e9..f2e55a33ef7 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Кодът за потвърждение е задължителен." }, + "webauthnCancelOrTimeout": { + "message": "Удостоверяването беше отменено или отне твърде много време. Моля, опитайте отново." + }, "invalidVerificationCode": { "message": "Грешен код за потвърждаване" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Грешка при свързването с услугата на Duo. Използвайте друг метод за двустепенно удостоверяване или се свържете с Duo за съдействие." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Стартирайте Duo и следвайте инструкциите, за да завършите вписването." }, diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index dd173da327f..fed7fb5babd 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "যাচাইকরণ কোড প্রয়োজন।" }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 72ad7db55de..27c2fd94a4e 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verifikacijski kod je neophodan." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Neispravan verifikacijski kod" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 1d733b87a95..d696fa2f4de 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "El codi de verificació és obligatori." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Codi de verificació no vàlid" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Inicieu DUO i seguiu els passos per finalitzar la sessió." }, diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 59535060372..104fe42ee97 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Je vyžadován ověřovací kód." }, + "webauthnCancelOrTimeout": { + "message": "Ověření bylo zrušeno nebo trvalo příliš dlouho. Zkuste to znovu." + }, "invalidVerificationCode": { "message": "Neplatný ověřovací kód" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Chyba při připojování ke službě Duo. Použijte jinou dvoufázovou metodu přihlášení nebo kontaktujte Duo o pomoc." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Spusťte DUO a pro dokončení přihlášení postupujte podle kroků." }, diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 8d6988f73e3..f3ec2e86180 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index 8c20ec2c4a2..082432368f4 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Bekræftelseskode er obligatorisk." }, + "webauthnCancelOrTimeout": { + "message": "Godkendelsen blev afbrudt eller tog for lang tid. Forsøg igen." + }, "invalidVerificationCode": { "message": "Ugyldig bekræftelseskode" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Fejl under forbindelsesoprettelsen til Duo-tjenesten. Brug en anden totrins-indlogningsmetode eller kontakt Duo for hjælp." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Start Duo og følg trinnene for at fuldføre indlogningen." }, diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index c52ff1d833a..8651b06434a 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verifizierungscode wird benötigt." }, + "webauthnCancelOrTimeout": { + "message": "Die Authentifizierung wurde abgebrochen oder hat zu lange gedauert. Bitte versuche es erneut." + }, "invalidVerificationCode": { "message": "Ungültiger Verifizierungscode" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Fehler beim Verbinden mit dem Duo-Dienst. Verwende eine andere Zwei-Faktor-Authentifizierungsmethode oder kontaktiere Duo für Hilfe." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Starte DUO und folge den Schritten, um die Anmeldung abzuschließen." }, diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index f1f950e3c22..546b87086f7 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -6,7 +6,7 @@ "message": "Φίλτρα" }, "allItems": { - "message": "Όλα τα στοιχεία" + "message": "Όλα τα αντικείμενα" }, "favorites": { "message": "Αγαπημένα" @@ -24,7 +24,7 @@ "message": "Ταυτότητα" }, "typeSecureNote": { - "message": "Ασφαλής Σημείωση" + "message": "Ασφαλής σημείωση" }, "folders": { "message": "Φάκελοι" @@ -33,10 +33,10 @@ "message": "Συλλογές" }, "searchVault": { - "message": "Αναζήτηση στο vault" + "message": "Αναζήτηση στο θησαυ/κιο" }, "addItem": { - "message": "Προσθήκη Στοιχείου" + "message": "Προσθήκη αντικειμένου" }, "shared": { "message": "Κοινοποιήθηκε" @@ -67,7 +67,7 @@ "message": "Συνημμένα" }, "viewItem": { - "message": "Προβολή Στοιχείου" + "message": "Προβολή αντικειμένου" }, "name": { "message": "Όνομα" @@ -98,10 +98,10 @@ "message": "Συνθηματικό" }, "editItem": { - "message": "Επεξεργασία Στοιχείου" + "message": "Επεξεργασία αντικειμένου" }, "emailAddress": { - "message": "Διεύθυνση Email" + "message": "Διεύθυνση ηλεκτρονικού ταχυδρομείου" }, "verificationCodeTotp": { "message": "Κωδικός Επαλήθευσης (TOTP)" @@ -119,24 +119,24 @@ "message": "Εκκίνηση" }, "copyValue": { - "message": "Αντιγραφή Τιμής", + "message": "Αντιγραφή τιμής", "description": "Copy value to clipboard" }, "minimizeOnCopyToClipboard": { "message": "Ελαχιστοποίηση κατά την αντιγραφή στο πρόχειρο" }, "minimizeOnCopyToClipboardDesc": { - "message": "Ελαχιστοποίηση κατά την αντιγραφή των δεδομένων ενός στοιχείου στο πρόχειρο." + "message": "Ελαχιστοποίηση της εφαρμογής κατά την αντιγραφή των δεδομένων ενός αντικειμένου στο πρόχειρο." }, "toggleVisibility": { - "message": "Εναλλαγή Ορατότητας" + "message": "Εναλλαγή ορατότητας" }, "toggleCollapse": { - "message": "Εναλλαγή Σύμπτυξης", + "message": "Εναλλαγή σύμπτυξης", "description": "Toggling an expand/collapse state." }, "cardholderName": { - "message": "Όνομα κατόχου της κάρτας" + "message": "Όνομα κατόχου κάρτας" }, "number": { "message": "Αριθμός" @@ -148,10 +148,10 @@ "message": "Λήξη" }, "securityCode": { - "message": "Κωδικός Ασφαλείας" + "message": "Κωδικός ασφαλείας" }, "identityName": { - "message": "Όνομα Ταυτότητας" + "message": "Όνομα ταυτότητας" }, "company": { "message": "Εταιρεία" @@ -160,10 +160,10 @@ "message": "ΑΜΚΑ" }, "passportNumber": { - "message": "Αριθμός Διαβατηρίου" + "message": "Αριθμός διαβατηρίου" }, "licenseNumber": { - "message": "Αριθμός Άδειας" + "message": "Αριθμός άδειας" }, "email": { "message": "Email" @@ -175,10 +175,10 @@ "message": "Διεύθυνση" }, "premiumRequired": { - "message": "Απαιτείται Έκδοση Premium" + "message": "Απαιτείται Premium" }, "premiumRequiredDesc": { - "message": "Για να χρησιμοποιήσετε αυτή τη λειτουργία, απαιτείται η έκδοση premium." + "message": "Για να χρησιμοποιήσετε αυτή τη λειτουργία, απαιτείται η Premium συνδρομή." }, "errorOccurred": { "message": "Παρουσιάστηκε σφάλμα." @@ -239,7 +239,7 @@ "message": "Κα" }, "mx": { - "message": "Mx στα Ελληνικά" + "message": "Κ" }, "dr": { "message": "Δρ" @@ -257,7 +257,7 @@ "message": "Άλλες" }, "generatePassword": { - "message": "Δημιουργία Κωδικού" + "message": "Δημιουργία κωδικού πρόσβασης" }, "type": { "message": "Τύπος" @@ -266,7 +266,7 @@ "message": "Όνομα" }, "middleName": { - "message": "Μεσαίο Όνομα" + "message": "Μεσαίο όνομα" }, "lastName": { "message": "Επώνυμο" @@ -290,7 +290,7 @@ "message": "Περιοχή / Νομός" }, "zipPostalCode": { - "message": "Ταχυδρομικός Κώδικας" + "message": "Ταχυδρομικός κώδικας" }, "country": { "message": "Χώρα" @@ -311,13 +311,13 @@ "message": "Επεξεργασία" }, "authenticatorKeyTotp": { - "message": "Κλειδί επαλήθευσης (TOTP)" + "message": "Κλειδί αυθεντικοποίησης (TOTP)" }, "folder": { "message": "Φάκελος" }, "newCustomField": { - "message": "Νέο Προσαρμοσμένο Πεδίο" + "message": "Νέο προσαρμοσμένο πεδίο" }, "value": { "message": "Τιμή" @@ -349,25 +349,25 @@ "message": "Απαιτείται όνομα." }, "addedItem": { - "message": "Το στοιχείο προστέθηκε" + "message": "Το αντικείμενο προστέθηκε" }, "editedItem": { - "message": "Το στοιχείο αποθηκεύτηκε" + "message": "Το αντικείμενο αποθηκεύτηκε" }, "deleteItem": { - "message": "Διαγραφή Στοιχείου" + "message": "Διαγραφή στοιχείου" }, "deleteFolder": { - "message": "Διαγραφή Φακέλου" + "message": "Διαγραφή φακέλου" }, "deleteAttachment": { - "message": "Διαγραφή Συνημμένου" + "message": "Διαγραφή συνημμένου" }, "deleteItemConfirmation": { - "message": "Είστε βέβαιοι ότι θέλετε να μετακινήσετε αυτό το στοιχείο στον κάδο απορριμμάτων;" + "message": "Είστε βέβαιοι ότι θέλετε να το μετακινήσετε στον κάδο;" }, "deletedItem": { - "message": "Το στοιχείο μετακινήθηκε στον κάδο απορριμάτων" + "message": "Το αντικείμενο μετακινήθηκε στον κάδο απορριμάτων" }, "overwritePasswordConfirmation": { "message": "Είστε βέβαιοι ότι θέλετε να αντικαταστήσετε τον τρέχον κωδικό πρόσβασης;" @@ -379,20 +379,20 @@ "message": "Είστε βέβαιοι ότι θέλετε να αντικαταστήσετε το τρέχον όνομα χρήστη;" }, "noneFolder": { - "message": "Χωρίς Φάκελο", + "message": "Χωρίς φάκελο", "description": "This is the folder for uncategorized items" }, "addFolder": { - "message": "Προσθήκη Φακέλου" + "message": "Προσθήκη φακέλου" }, "editFolder": { - "message": "Επεξεργασία Φακέλου" + "message": "Επεξεργασία φακέλου" }, "regeneratePassword": { - "message": "Επαναδημιουργία Κωδικού" + "message": "Επαναδημιουργία κωδικού πρόσβασης" }, "copyPassword": { - "message": "Αντιγραφή Κωδικού" + "message": "Αντιγραφή κωδικού πρόσβασης" }, "copyUri": { "message": "Αντιγραφή URI" @@ -404,7 +404,7 @@ "message": "Μήκος" }, "passwordMinLength": { - "message": "Ελάχιστο μήκος κωδικού" + "message": "Ελάχιστο μήκος κωδικού πρόσβασης" }, "uppercase": { "message": "Κεφαλαία (A-Z)" @@ -416,13 +416,13 @@ "message": "Αριθμοί (0-9)" }, "specialCharacters": { - "message": "Ειδικοί Χαρακτήρες (!@#$%^&*)" + "message": "Ειδικοί χαρακτήρες (!@#$%^&*)" }, "numWords": { - "message": "Αριθμός Λέξεων" + "message": "Αριθμός λέξεων" }, "wordSeparator": { - "message": "Διαχωριστής Λέξεων" + "message": "Διαχωριστής λέξεων" }, "capitalize": { "message": "Κεφαλαία", @@ -435,30 +435,30 @@ "message": "Κλείσιμο" }, "minNumbers": { - "message": "Ελάχιστα Αριθμητικά Ψηφία" + "message": "Ελάχιστα αριθμητικά ψηφία" }, "minSpecial": { "message": "Ελάχιστοι ειδικοί χαρακτήρες", "description": "Minimum Special Characters" }, "ambiguous": { - "message": "Αποφυγή Αμφιλεγόμενων Χαρακτήρων" + "message": "Αποφυγή αμφιλεγόμενων χαρακτήρων" }, "searchCollection": { - "message": "Αναζήτηση στη Συλλογή" + "message": "Αναζήτηση στη συλλογή" }, "searchFolder": { - "message": "Αναζήτηση στον Φάκελο" + "message": "Αναζήτηση στο φάκελο" }, "searchFavorites": { - "message": "Αναζήτηση στα Αγαπημένα" + "message": "Αναζήτηση στα αγαπημένα" }, "searchType": { - "message": "Αναζήτηση σε αυτόν τον τύπο", + "message": "Αναζήτηση τύπου", "description": "Search item type" }, "newAttachment": { - "message": "Προσθήκη Νέου Συνημμένου" + "message": "Προσθήκη νέου συνημμένου" }, "deletedAttachment": { "message": "Το συνημμένο διαγράφηκε" @@ -467,7 +467,7 @@ "message": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το συνημμένο;" }, "attachmentSaved": { - "message": "Το συννημένο αποθηκεύτηκε" + "message": "Το συνημμένο αποθηκεύτηκε" }, "file": { "message": "Αρχείο" @@ -479,7 +479,7 @@ "message": "Το μέγιστο μέγεθος αρχείου είναι 500 MB." }, "encryptionKeyMigrationRequired": { - "message": "Απαιτείται μεταφορά κλειδιού κρυπτογράφησης. Παρακαλούμε συνδεθείτε μέσω του διαδικτυακής κρύπτης για να ενημερώσετε το κλειδί κρυπτογράφησης." + "message": "Απαιτείται μεταφορά κλειδιού κρυπτογράφησης. Παρακαλούμε συνδεθείτε μέσω του διαδικτυακού θησαυ/κίου για να ενημερώσετε το κλειδί κρυπτογράφησης σας." }, "editedFolder": { "message": "Ο φάκελος αποθηκεύτηκε" @@ -488,7 +488,7 @@ "message": "Ο φάκελος προστέθηκε" }, "deleteFolderConfirmation": { - "message": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον φάκελο;" + "message": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτόν τον φάκελο;" }, "deletedFolder": { "message": "Ο φάκελος διαγράφηκε" @@ -497,13 +497,13 @@ "message": "Συνδεθείτε ή δημιουργήστε ένα νέο λογαριασμό για να αποκτήσετε πρόσβαση στο ασφαλές vault σας." }, "createAccount": { - "message": "Δημιουργία Λογαριασμού" + "message": "Δημιουργία λογαριασμού" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Ορίστε έναν ισχυρό κωδικό πρόσβασης" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "Ολοκληρώστε τη δημιουργία του λογαριασμού σας ορίζοντας έναν κωδικό πρόσβασης" }, "logIn": { "message": "Είσοδος" @@ -512,7 +512,7 @@ "message": "Υποβολή" }, "masterPass": { - "message": "Κύριος Κωδικός" + "message": "Κύριος κωδικός πρόσβασης" }, "masterPassDesc": { "message": "Ο κύριος κωδικός είναι ο κωδικός που χρησιμοποιείτε για την πρόσβαση στο vault σας. Είναι πολύ σημαντικό να μην ξεχάσετε τον κύριο κωδικό. Δεν υπάρχει τρόπος να ανακτήσετε τον κωδικό σε περίπτωση που τον ξεχάσετε." @@ -521,13 +521,13 @@ "message": "Η υπόδειξη κύριου κωδικού μπορεί να σας βοηθήσει να θυμηθείτε τον κωδικό σας αν τον ξεχάσετε." }, "reTypeMasterPass": { - "message": "Επιβεβαίωση κύριου κωδικού" + "message": "Εισάγετε ξανά τον κύριο κωδικό πρόσβασης" }, "masterPassHint": { - "message": "Υπόδειξη Κύριου Κωδικού (προαιρετικό)" + "message": "Υπόδειξη κύριου κωδικού πρόσβασης (προαιρετικό)" }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "Αν ξεχάσετε τον κωδικό πρόσβασής σας, η υπενθύμιση του κωδικού πρόσβασης μπορεί να σταλεί στο email σας. $CURRENT$/$MAXIMUM$ μέγιστοι χαρακτήρες.", "placeholders": { "current": { "content": "$1", @@ -540,25 +540,25 @@ } }, "masterPassword": { - "message": "Master password" + "message": "Κύριος κωδικός πρόσβασης" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "Ο κύριος κωδικός πρόσβασής σας δεν μπορεί να ανακτηθεί αν τον ξεχάσετε!" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "Επιβεβαίωση κύριου κωδικού πρόσβασης" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Υπόδειξη κύριου κωδικού πρόσβασης" }, "settings": { "message": "Ρυθμίσεις" }, "passwordHint": { - "message": "Υπόδειξη Κωδικού" + "message": "Υπόδειξη κωδικού πρόσβασης" }, "enterEmailToGetHint": { - "message": "Εισάγετε τη διεύθυνση email του λογαριασμού σας για να λάβετε την υπόδειξη του κύριου κωδικού." + "message": "Εισάγετε τη διεύθυνση ηλεκτρονικού ταχυδρομείου του λογαριασμού σας για να λάβετε την υπόδειξη του κύριου κωδικού πρόσβασης." }, "getMasterPasswordHint": { "message": "Λήψη υπόδειξης κύριου κωδικού" @@ -570,13 +570,13 @@ "message": "Μη έγκυρη διεύθυνση e-mail." }, "masterPasswordRequired": { - "message": "Απαιτείται ο κύριος κωδικός." + "message": "Απαιτείται ο κύριος κωδικός πρόσβασης." }, "confirmMasterPasswordRequired": { - "message": "Απαιτείται επιβεβαίωση του κύριου κωδικού." + "message": "Απαιτείται επαναπληκτρολόγηση του κύριου κωδικού πρόσβασης." }, "masterPasswordMinlength": { - "message": "Ο κύριος κωδικός πρέπει να έχει τουλάχιστον $VALUE$ χαρακτήρες μήκος.", + "message": "Ο κύριος κωδικός πρόσβασης πρέπει να έχει τουλάχιστον $VALUE$ χαρακτήρες μήκος.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -604,7 +604,7 @@ "message": "Παρουσιάστηκε ένα μη αναμενόμενο σφάλμα." }, "itemInformation": { - "message": "Πληροφορίες Στοιχείου" + "message": "Πληροφορίες αντικειμένου" }, "noItemsInList": { "message": "Δεν υπάρχουν στοιχεία στη λίστα." @@ -613,13 +613,13 @@ "message": "Στείλτε έναν κωδικό επαλήθευσης στο email σας" }, "sendCode": { - "message": "Αποστολή Κωδικού" + "message": "Αποστολή κωδικού" }, "codeSent": { - "message": "Ο Κωδικός Στάλθηκε" + "message": "Ο κωδικός στάλθηκε" }, "verificationCode": { - "message": "Κωδικός Επαλήθευσης" + "message": "Κωδικός επαλήθευσης" }, "confirmIdentity": { "message": "Επιβεβαιώστε την ταυτότητα σας για να συνεχίσετε." @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Απαιτείται ο κωδικός επαλήθευσης." }, + "webauthnCancelOrTimeout": { + "message": "Η αυθεντικοποίηση ακυρώθηκε ή διήρκησε πολύ ώρα. Παρακαλώ προσπαθήστε ξανά." + }, "invalidVerificationCode": { "message": "Μη έγκυρος κωδικός επαλήθευσης" }, @@ -658,38 +661,38 @@ "message": "Να με θυμάσαι" }, "sendVerificationCodeEmailAgain": { - "message": "Επανάληψη αποστολής email με κωδικό επαλήθευσης" + "message": "Επανάληψη αποστολής κωδικού επαλήθευσης μέσω ηλεκτρονικού ταχυδρομείου" }, "useAnotherTwoStepMethod": { "message": "Χρήση άλλης μεθόδου σύνδεσης δύο βημάτων" }, "insertYubiKey": { - "message": "Τοποθετήστε το YubiKey στη θύρα USB του υπολογιστή σας και έπειτα πατήστε το κουμπί του." + "message": "Τοποθετήστε το YubiKey στη θύρα USB του υπολογιστή σας και έπειτα αγγίξτε το κουμπί του." }, "insertU2f": { "message": "Εισάγετε το κλειδί ασφαλείας στη θύρα USB του υπολογιστή σας. Αν έχει κουμπί, πατήστε το." }, "recoveryCodeDesc": { - "message": "Έχετε χάσει την πρόσβαση σε όλους τους παρόχους δύο παραγόντων; Χρησιμοποιήστε τον κωδικό ανάκτησης για να απενεργοποιήσετε όλους τους παρόχους δύο παραγόντων από το λογαριασμό σας." + "message": "Έχετε χάσει την πρόσβαση σε όλους τους παρόχους δύο παραγόντων; Χρησιμοποιήστε τον κωδικό ανάκτησης σας για να απενεργοποιήσετε όλους τους παρόχους δύο παραγόντων από τον λογαριασμό σας." }, "recoveryCodeTitle": { - "message": "Κωδικός Ανάκτησης" + "message": "Κωδικός ανάκτησης" }, "authenticatorAppTitle": { - "message": "Εφαρμογή Επαλήθευσης" + "message": "Εφαρμογή αυθεντικοποίησης" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "Εισάγετε έναν κωδικό που δημιουργήθηκε από μια εφαρμογή αυθεντικοποίησης όπως το Bitwarden Authenticator.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "Κλειδί ασφαλείας YubiKey OTP" }, "yubiKeyDesc": { "message": "Χρησιμοποιήστε ένα YubiKey για να αποκτήσετε πρόσβαση στο λογαριασμό σας. Λειτουργεί με συσκευές σειράς YubiKey 4, 4 Nano, 4C και συσκευές NEO." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "Εισάγετε έναν κωδικό που δημιουργήθηκε από το Duo Security.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -700,19 +703,19 @@ "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "Χρησιμοποιήστε οποιοδήποτε κλειδί ασφαλείας WebAuthn για να αποκτήσετε πρόσβαση στο λογαριασμό σας." + "message": "Χρησιμοποιήστε οποιοδήποτε συμβατό κλειδί ασφαλείας WebAuthn για να αποκτήσετε πρόσβαση στον λογαριασμό σας." }, "emailTitle": { "message": "Email" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Εισάγετε έναν κωδικό που σας στάλθηκε στο email." }, "loginUnavailable": { - "message": "Σύνδεση μη διαθέσιμη" + "message": "Μη διαθέσιμη σύνδεση" }, "noTwoStepProviders": { - "message": "Αυτός ο λογαριασμός έχει ενεργοποιημένη τη σύνδεση σε δύο βήματα, ωστόσο, κανένας από τους καθορισμένους παρόχους δύο βημάτων δεν υποστηρίζεται από αυτήν τη συσκευή." + "message": "Αυτός ο λογαριασμός έχει ενεργοποιημένη τη σύνδεση δύο βημάτων, ωστόσο, κανένας από τους ρυθμισμένους παρόχους δύο βημάτων δεν υποστηρίζεται από αυτήν τη συσκευή." }, "noTwoStepProviders2": { "message": "Προσθέστε επιπλέον παρόχους που υποστηρίζονται καλύτερα σε όλες τις συσκευές (όπως μια εφαρμογή επαλήθευσης)." @@ -721,7 +724,7 @@ "message": "Επιλογές σύνδεσης δύο βημάτων" }, "selfHostedEnvironment": { - "message": "Αυτο-Φιλοξενούμενο Περιβάλλον" + "message": "Αυτο-φιλοξενούμενο περιβάλλον" }, "selfHostedEnvironmentFooter": { "message": "Καθορίστε τη βασική διεύθυνση URL, της εγκατάστασης του Bitwarden που φιλοξενείται στο χώρο σας." @@ -730,10 +733,10 @@ "message": "Καθορίστε τη βασική διεύθυνση URL της εγκατάστασης Bitwarden στον τομέα σας. Παράδειγμα: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "Για προχωρημένη ρύθμιση, μπορείτε να ορίσετε ανεξάρτητα τη βασική διεύθυνση URL κάθε υπηρεσίας." + "message": "Για προχωρημένη παραμετροποίηση, μπορείτε να ορίσετε ανεξάρτητα το βασικό URL κάθε υπηρεσίας." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "Πρέπει να προσθέσετε είτε το βασικό URL του διακομιστή ή τουλάχιστον ένα προσαρμοσμένο περιβάλλον." }, "customEnvironment": { "message": "Προσαρμοσμένο περιβάλλον" @@ -745,10 +748,10 @@ "message": "URL Διακομιστή" }, "apiUrl": { - "message": "URL Διακομιστή API" + "message": "URL διακομιστή API" }, "webVaultUrl": { - "message": "URL διακομιστή web vault" + "message": "URL διακομιστή διαδικτυακού θησαυ/κίου" }, "identityUrl": { "message": "URL διακομιστή ταυτότητας" @@ -760,7 +763,7 @@ "message": "URL διακομιστή εικονιδίων" }, "environmentSaved": { - "message": "Οι διευθύνσεις URL περιβάλλοντος έχουν αποθηκευτεί." + "message": "Τα URL περιβάλλοντος έχουν αποθηκευτεί" }, "ok": { "message": "Οκ" @@ -772,19 +775,19 @@ "message": "Όχι" }, "overwritePassword": { - "message": "Αντικατάσταση Κωδικού Πρόσβασης" + "message": "Αντικατάσταση κωδικού πρόσβασης" }, "learnMore": { "message": "Μάθετε περισσότερα" }, "featureUnavailable": { - "message": "Μη Διαθέσιμο Χαρακτηριστικό" + "message": "Μη διαθέσιμη λειτουργία" }, "loggedOut": { "message": "Αποσύνδεση" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Έχετε αποσυνδεθεί από τον λογαριασμό σας." }, "loginExpired": { "message": "Η περίοδος σύνδεσης σας έχει λήξει." @@ -796,13 +799,13 @@ "message": "Αποσύνδεση" }, "addNewLogin": { - "message": "Προσθήκη Νέας Σύνδεσης" + "message": "Νέα σύνδεση" }, "addNewItem": { - "message": "Προσθήκη Νέου Στοιχείου" + "message": "Νέο αντικείμενο" }, "addNewFolder": { - "message": "Προσθήκη Νέου Φακέλου" + "message": "Νέος φάκελος" }, "view": { "message": "Προβολή" @@ -814,22 +817,22 @@ "message": "Φόρτωση..." }, "lockVault": { - "message": "Κλείδωμα Vault" + "message": "Κλείδωμα θησαυ/κίου" }, "passwordGenerator": { - "message": "Γεννήτρια Κωδικού" + "message": "Γεννήτρια κωδικού πρόσβασης" }, "contactUs": { - "message": "Επικοινωνία" + "message": "Επικοινωνήστε μαζί μας" }, "helpAndFeedback": { "message": "Βοήθεια και σχόλια" }, "getHelp": { - "message": "Ζητήστε Βοήθεια" + "message": "Ζητήστε βοήθεια" }, "fileBugReport": { - "message": "Υποβολή Αναφοράς Σφάλματος" + "message": "Καταγράψτε μια αναφορά σφάλματος" }, "blog": { "message": "Blog" @@ -838,19 +841,19 @@ "message": "Ακολουθήστε μας" }, "syncVault": { - "message": "Συγχρονισμός Vault" + "message": "Συγχρονισμός θησαυ/κίου" }, "changeMasterPass": { - "message": "Αλλαγή Κύριου Κωδικού" + "message": "Αλλαγή κύριου κωδικού πρόσβασης" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Συνέχεια στη διαδικτυακή εφαρμογή;" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Μπορείτε να αλλάξετε τον κύριο κωδικό πρόσβασής σας στη διαδικτυακή εφαρμογή του Bitwarden." }, "fingerprintPhrase": { - "message": "Φράση Δακτυλικών Αποτυπωμάτων", + "message": "Φράση δακτυλικών αποτυπωμάτων", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "yourAccountsFingerprint": { @@ -858,13 +861,13 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "goToWebVault": { - "message": "Πηγαίνετε στο Web Vault" + "message": "Πηγαίνετε στο διαδικτυακό θησαυ/κιο" }, "getMobileApp": { - "message": "Κατεβάστε την εφαρμογή για κινητά" + "message": "Λήψη εφαρμογής για το κινητό" }, "getBrowserExtension": { - "message": "Κατεβάστε την επέκταση προγράμματος περιήγησης" + "message": "Λήψη επέκτασης περιηγητή" }, "syncingComplete": { "message": "Ο συγχρονισμός ολοκληρώθηκε" @@ -873,7 +876,7 @@ "message": "Ο συγχρονισμός απέτυχε" }, "yourVaultIsLocked": { - "message": "Το vault σας είναι κλειδωμένο. Επαληθεύστε την ταυτότητά σας για να συνεχίσετε." + "message": "Το θησαυ/κιό σας είναι κλειδωμένο. Επαληθεύστε την ταυτότητά σας για να συνεχίσετε." }, "unlock": { "message": "Ξεκλείδωμα" @@ -895,16 +898,16 @@ "message": "Μη έγκυρος κύριος κωδικός πρόσβασης" }, "twoStepLoginConfirmation": { - "message": "Η σύνδεση σε δύο βήματα καθιστά ασφαλέστερο τον λογαριασμό σας, απαιτώντας να επαληθεύσετε τα στοιχεία σας με μια άλλη συσκευή, όπως ένα κλειδί ασφαλείας, εφαρμογή επαλήθευσης ταυτότητας, SMS, τηλεφωνική κλήση, ή email. Μπορείτε να ενεργοποιήσετε τη σύνδεση σε δύο βήματα στο web vault στο bitwarden.com. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;" + "message": "Η σύνδεση δύο βημάτων καθιστά τον λογαριασμό σας πιο ασφαλή απαιτώντας από εσάς να επαληθεύσετε τη σύνδεσή σας με άλλη συσκευή, όπως ένα κλειδί ασφαλείας, μία εφαρμογή αυθεντικοποίησης, ένα SMS, μία τηλεφωνική κλήση, ή ένα μήνυμα ηλεκτρονικού ταχυδρομείου. Η σύνδεση δύο βημάτων μπορεί να ρυθμιστεί στο διαδικτυακό θησαυ/κιο bitwarden.com. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;" }, "twoStepLogin": { - "message": "Σύνδεση σε δύο βήματα" + "message": "Σύνδεση δύο βημάτων" }, "vaultTimeout": { - "message": "Χρόνος Λήξης Vault" + "message": "Χρονικό όριο λήξης θησαυ/κίου" }, "vaultTimeoutDesc": { - "message": "Επιλέξτε πότε θα πραγματοποιείται η επιλεγμένη ενέργεια χρόνου λήξης vault." + "message": "Επιλέξτε πότε το θησαυ/κιό σας θα αναλάβει τη δράση χρονικού ορίου λήξης θησαυ/κίου." }, "immediately": { "message": "Άμεσα" @@ -940,16 +943,16 @@ "message": "4 ώρες" }, "onIdle": { - "message": "Κατά την Αδράνεια Συστήματος" + "message": "Σε αδράνεια συστήματος" }, "onSleep": { - "message": "Κατά την Αναμονή Συστήματος" + "message": "Κατά την αναμονή συστήματος" }, "onLocked": { - "message": "Κατά το Κλείδωμα Συστήματος" + "message": "Στο κλείδωμα συστήματος" }, "onRestart": { - "message": "Κατά την Επανεκκίνηση" + "message": "Κατά την επανεκκίνηση" }, "never": { "message": "Ποτέ" @@ -958,7 +961,7 @@ "message": "Ασφάλεια" }, "clearClipboard": { - "message": "Εκκαθάριση Πρόχειρου", + "message": "Εκκαθάριση πρόχειρου", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "clearClipboardDesc": { @@ -996,7 +999,7 @@ "message": "Κατά το κλείσιμο του παραθύρου, εμφανίζεται ένα εικονίδιο στη γραμμή μενού." }, "enableTray": { - "message": "Ενεργοποίηση εικονιδίου περιοχής ειδοποιήσεων" + "message": "Εμφάνιση εικονιδίου στην περιοχή ειδοποιήσεων" }, "enableTrayDesc": { "message": "Να εμφανίζεται πάντα ένα εικονίδιο στην περιοχή ειδοποιήσεων." @@ -1017,7 +1020,7 @@ "message": "Εκκίνηση αυτόματα κατά τη σύνδεση" }, "openAtLoginDesc": { - "message": "Εκκίνηση της εφαρμογής Bitwarden Desktop αυτόματα κατά τη σύνδεση." + "message": "Εκκίνηση της εφαρμογής Bitwarden στην επιφάνεια εργασίας αυτόματα κατά τη σύνδεση." }, "alwaysShowDock": { "message": "Να εμφανίζεται πάντα στο Dock" @@ -1026,10 +1029,10 @@ "message": "Εμφάνιση του εικονιδίου Bitwarden στο Dock ακόμα και όταν ελαχιστοποιείται στη γραμμή μενού." }, "confirmTrayTitle": { - "message": "Επιβεβαίωση απενεργοποίησης συστήματος" + "message": "Επιβεβαίωση απόκρυψης γραμμής συστήματος" }, "confirmTrayDesc": { - "message": "Η απενεργοποίηση αυτής της ρύθμισης θα απενεργοποιήσει επίσης όλες τις άλλες ρυθμίσεις που σχετίζονται με το δίσκο." + "message": "Η απενεργοποίηση αυτής της ρύθμισης θα απενεργοποιήσει επίσης όλες τις άλλες ρυθμίσεις που σχετίζονται με τη γραμμή συστήματος." }, "language": { "message": "Γλώσσα" @@ -1056,7 +1059,7 @@ "description": "Copy to clipboard" }, "checkForUpdates": { - "message": "Έλεγχος Για Ενημερώσεις" + "message": "Έλεγχος για ενημερώσεις…" }, "version": { "message": "Έκδοση $VERSION_NUM$", @@ -1068,7 +1071,7 @@ } }, "restartToUpdate": { - "message": "Κάντε επανεκκίνηση για ενημέρωση" + "message": "Επανεκκίνηση για ενημέρωση" }, "restartToUpdateDesc": { "message": "Η έκδοση $VERSION_NUM$ είναι έτοιμη για εγκατάσταση. Θα πρέπει να επανεκκινήσετε την εφαρμογή για να ολοκληρωθεί η εγκατάσταση. Θέλετε να κάνετε επανεκκίνηση και ενημέρωση τώρα;", @@ -1080,7 +1083,7 @@ } }, "updateAvailable": { - "message": "Διαθέσιμη Ενημέρωση" + "message": "Διαθέσιμη ενημέρωση" }, "updateAvailableDesc": { "message": "Βρέθηκε μια ενημέρωση. Θέλετε να την κατεβάσετε τώρα;" @@ -1095,39 +1098,39 @@ "message": "Δεν υπάρχουν προς το παρόν διαθέσιμες ενημερώσεις. Χρησιμοποιείτε την τελευταία έκδοση." }, "updateError": { - "message": "Σφάλμα Ενημέρωσης" + "message": "Σφάλμα ενημέρωσης" }, "unknown": { "message": "Άγνωστο" }, "copyUsername": { - "message": "Αντιγραφή Ονόματος Χρήστη" + "message": "Αντιγραφή ονόματος χρήστη" }, "copyNumber": { - "message": "Αντιγραφή Αριθμού", + "message": "Αντιγραφή αριθμού", "description": "Copy credit card number" }, "copySecurityCode": { - "message": "Αντιγραφή Κωδικού Ασφαλείας", + "message": "Αντιγραφή κωδικού ασφαλείας", "description": "Copy credit card security code (CVV)" }, "premiumMembership": { "message": "Συνδρομή Premium" }, "premiumManage": { - "message": "Διαχείριση Συνδρομής" + "message": "Διαχείριση συνδρομής" }, "premiumManageAlert": { "message": "Μπορείτε να διαχειριστείτε την ιδιότητά σας ως μέλος στο bitwarden.com web vault. Θέλετε να επισκεφθείτε την ιστοσελίδα τώρα;" }, "premiumRefresh": { - "message": "Ανανέωση Συνδρομής" + "message": "Ανανέωση συνδρομής" }, "premiumNotCurrentMember": { - "message": "Δεν είστε premium μέλος." + "message": "Δεν είστε Premium μέλος αυτήν τη στιγμή." }, "premiumSignUpAndGet": { - "message": "Εγγραφείτε για μια premium συνδρομή και λάβετε:" + "message": "Εγγραφείτε για μια Premium συνδρομή και λάβετε:" }, "premiumSignUpStorage": { "message": "1 GB κρυπτογραφημένο αποθηκευτικό χώρο για συνημμένα αρχεία." @@ -1172,7 +1175,7 @@ "message": "Επιτυχής ανανέωση" }, "passwordHistory": { - "message": "Ιστορικό Κωδικού" + "message": "Ιστορικό κωδικού" }, "clear": { "message": "Εκκαθάριση", @@ -1196,7 +1199,7 @@ "description": "Paste from clipboard" }, "selectAll": { - "message": "Επιλογή Όλων" + "message": "Επιλογή όλων" }, "zoomIn": { "message": "Μεγέθυνση" @@ -1205,16 +1208,16 @@ "message": "Σμίκρυνση" }, "resetZoom": { - "message": "Επαναφορά Μεγέθυνσης" + "message": "Επαναφορά μεγέθυνσης" }, "toggleFullScreen": { - "message": "Εναλλαγή σε Πλήρη Οθόνη" + "message": "Εναλλαγή πλήρους οθόνης" }, "reload": { "message": "Επαναφόρτωση" }, "toggleDevTools": { - "message": "Εναλλαγή σε Εργαλεία Προγραμματιστή" + "message": "Εναλλαγή εργαλείων προγραμματιστή" }, "minimize": { "message": "Ελαχιστοποίηση", @@ -1224,7 +1227,7 @@ "message": "Μεγέθυνση" }, "bringAllToFront": { - "message": "Φέρτε τα όλα σε πρώτο πλάνο", + "message": "Μεταφορά όλων στο προσκήνιο", "description": "Bring all windows to front (foreground)" }, "aboutBitwarden": { @@ -1237,10 +1240,10 @@ "message": "Απόκρυψη Bitwarden" }, "hideOthers": { - "message": "Απόκρυψη Άλλων" + "message": "Απόκρυψη άλλων" }, "showAll": { - "message": "Εμφάνιση Όλων" + "message": "Εμφάνιση όλων" }, "quitBitwarden": { "message": "Έξοδος Bitwarden" @@ -1256,10 +1259,10 @@ } }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "Σφάλμα Ανανέωσης Διακριτικού Πρόσβασης" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Δεν βρέθηκε διακριτικό ανανέωσης ή κλειδιά API. Παρακαλούμε δοκιμάστε να αποσυνδεθείτε και να συνδεθείτε ξανά." }, "help": { "message": "Βοήθεια" @@ -1305,7 +1308,7 @@ "description": "A programming term, also known as 'RegEx'." }, "matchDetection": { - "message": "Εντοπισμός Αντιστοίχισης", + "message": "Εντοπισμός αντιστοίχισης", "description": "URI match detection for auto-fill." }, "defaultMatchDetection": { @@ -1313,7 +1316,7 @@ "description": "Default URI match detection for auto-fill." }, "toggleOptions": { - "message": "Επιλογές Εναλλαγής" + "message": "Εναλλαγή επιλογών" }, "organization": { "message": "Οργανισμός", @@ -1330,10 +1333,10 @@ "description": "Text for a button that toggles the visibility of the window. Shows the window when it is hidden or hides the window if it is currently open." }, "hideToTray": { - "message": "Απόκρυψη στο Δίσκο" + "message": "Απόκρυψη στη γραμμή συστήματος" }, "alwaysOnTop": { - "message": "Πάντα στη Κορυφή", + "message": "Πάντα στο προσκήνιο", "description": "Application window should always stay on top of other windows" }, "dateUpdated": { @@ -1345,44 +1348,44 @@ "description": "ex. Date this item was created" }, "datePasswordUpdated": { - "message": "Ο Κωδικός Ενημερώθηκε", + "message": "Ο κωδικός ενημερώθηκε", "description": "ex. Date this password was updated" }, "exportFrom": { - "message": "Export from" + "message": "Εξαγωγή από" }, "exportVault": { - "message": "Εξαγωγή Vault" + "message": "Εξαγωγή θησαυ/κίου" }, "fileFormat": { - "message": "Μορφή Αρχείου" + "message": "Τύπος αρχείου" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "Αυτή η εξαγωγή αρχείου θα προστατεύεται με κωδικό πρόσβασης και θα απαιτείται ο κωδικός πρόσβασης του αρχείου για αποκρυπτογράφηση." }, "filePassword": { - "message": "File password" + "message": "Κωδικός πρόσβασης αρχείου" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "Αυτός ο κωδικός πρόσβασης θα χρησιμοποιηθεί για την εξαγωγή και εισαγωγή αυτού του αρχείου" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "Χρησιμοποιήστε το κλειδί κρυπτογράφησης του λογαριασμού σας, που προέρχεται από το όνομα χρήστη και τον Κύριο Κωδικό Πρόσβασης του λογαριασμού σας, για να κρυπτογραφήσετε την εξαγωγή και να περιορίσετε την εισαγωγή μόνο στον τρέχοντα λογαριασμό Bitwarden." }, "passwordProtected": { - "message": "Password protected" + "message": "Προστατευμένο με κωδικό πρόσβασης" }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "Ορίστε έναν κωδικό πρόσβασης αρχείου για να κρυπτογραφήσετε την εξαγωγή και να τον εισάγετε σε οποιονδήποτε λογαριασμό Bitwarden χρησιμοποιώντας τον κωδικό πρόσβασης για αποκρυπτογράφηση." }, "exportTypeHeading": { - "message": "Export type" + "message": "Τύπος εξαγωγής" }, "accountRestricted": { - "message": "Account restricted" + "message": "Ο λογαριασμός περιορίστηκε" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "“Κωδικός πρόσβασης αρχείου” και “Επιβεβαίωση κωδικού πρόσβασης αρχείου“ δεν ταιριάζουν." }, "hCaptchaUrl": { "message": "hCaptcha Url", @@ -1423,7 +1426,7 @@ "description": "WARNING (should stay in capitalized letters if the language permits)" }, "confirmVaultExport": { - "message": "Επιβεβαίωση εξαγωγής Vault" + "message": "Επιβεβαίωση εξαγωγής θησαυ/κίου" }, "exportWarningDesc": { "message": "Αυτή η εξαγωγή περιέχει τα δεδομένα σε μη κρυπτογραφημένη μορφή. Δεν πρέπει να αποθηκεύετε ή να στείλετε το εξαγόμενο αρχείο μέσω μη ασφαλών τρόπων (όπως μέσω email). Διαγράψτε το αμέσως μόλις τελειώσετε με τη χρήση του." @@ -1459,7 +1462,7 @@ "description": "ex. A weak password. Scale: Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "Αδύναμος Κύριος Κωδικός" + "message": "Ασθενής κύριος κωδικός πρόσβασης" }, "weakMasterPasswordDesc": { "message": "Ο κύριος κωδικός που έχετε επιλέξει είναι αδύναμος. Θα πρέπει να χρησιμοποιήσετε έναν ισχυρό κύριο κωδικό (ή μια φράση) για την κατάλληλη προστασία του λογαριασμού Bitwarden. Είστε βέβαιοι ότι θέλετε να χρησιμοποιήσετε αυτόν τον κύριο κωδικό;" @@ -1520,10 +1523,10 @@ "message": "Διαγραφή λογαριασμού" }, "deleteAccountDesc": { - "message": "Προχωρήστε παρακάτω για να διαγράψετε το λογαριασμό σας και όλα τα δεδομένα θησαυ/κιού." + "message": "Προχωρήστε παρακάτω για να διαγράψετε τον λογαριασμό σας και όλα τα δεδομένα θησαυ/κίου." }, "deleteAccountWarning": { - "message": "Η διαγραφή του λογαριασμού σας είναι μόνιμη. Δε μπορεί να αναιρεθεί." + "message": "Η διαγραφή του λογαριασμού σας είναι μόνιμη. Δεν μπορεί να αναιρεθεί." }, "accountDeleted": { "message": "Ο λογαριασμός διαγράφηκε" @@ -1535,19 +1538,19 @@ "message": "Προτιμήσεις" }, "enableMenuBar": { - "message": "Ενεργοποίηση Εικονιδίου Μπάρας Μενού" + "message": "Εμφάνιση εικονιδίου γραμμής μενού" }, "enableMenuBarDesc": { "message": "Να εμφανίζεται πάντα το εικονίδιο στη μπάρα μενού." }, "hideToMenuBar": { - "message": "Απόκρυψη στη Μπάρα Μενού" + "message": "Απόκρυψη στη γραμμή μενού" }, "selectOneCollection": { "message": "Πρέπει να επιλέξετε τουλάχιστον μία συλλογή." }, "premiumUpdated": { - "message": "Έχετε αναβαθμίσει σε premium." + "message": "Έχετε αναβαθμιστεί σε Premium." }, "restore": { "message": "Επαναφορά" @@ -1578,16 +1581,16 @@ "message": "Μία ή περισσότερες πολιτικές του οργανισμού επηρεάζουν τις ρυθμίσεις της γεννήτριας." }, "vaultTimeoutAction": { - "message": "Ενέργεια Χρόνου Λήξης Vault" + "message": "Ενέργεια χρονικού ορίου λήξης θησαυ/κίου" }, "vaultTimeoutActionLockDesc": { - "message": "Ένα κλειδωμένο vault απαιτεί να εισάγετε ξανά τον κύριο κωδικό για να αποκτήσετε ξανά πρόσβαση σε αυτόν." + "message": "Απαιτείται κύριος κωδικός πρόσβασης ή άλλη μέθοδος ξεκλειδώματος για να αποκτήσετε ξανά πρόσβαση στο θησαυ/κιό σας." }, "vaultTimeoutActionLogOutDesc": { - "message": "Ένα αποσυνδεδεμένο vault απαιτεί να κάνετε ξανά έλεγχο ταυτότητας για να αποκτήσετε πρόσβαση σε αυτό." + "message": "Απαιτείται αυθεντικοποίηση για να αποκτήσετε ξανά πρόσβαση στο θησαυ/κιό σας." }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Ρυθμίστε μια μέθοδο ξεκλειδώματος για να αλλάξετε την ενέργεια χρονικού ορίου κρύπτης." + "message": "Ρυθμίστε μια μέθοδο ξεκλειδώματος για να αλλάξετε την ενέργεια χρονικού ορίου λήξης θησαυ/κίου." }, "lock": { "message": "Κλείδωμα", @@ -1598,34 +1601,34 @@ "description": "Noun: a special folder to hold deleted items" }, "searchTrash": { - "message": "Αναζήτηση Κάδου" + "message": "Αναζήτηση κάδου απορριμμάτων" }, "permanentlyDeleteItem": { - "message": "Μόνιμη Διαγραφή Αντικειμένου" + "message": "Μόνιμη διαγραφή αντικειμένου" }, "permanentlyDeleteItemConfirmation": { "message": "Είστε βέβαιοι ότι θέλετε να διαγράψετε μόνιμα αυτό το στοιχείο;" }, "permanentlyDeletedItem": { - "message": "Μόνιμα Διεγραμμένο Στοιχείο" + "message": "Το αντικείμενο διαγράφηκε οριστικά" }, "restoredItem": { - "message": "Στοιχείο που έχει Ανακτηθεί" + "message": "Το αντικείμενο επαναφέρθηκε" }, "permanentlyDelete": { - "message": "Μόνιμη Διαγραφή" + "message": "Μόνιμη διαγραφή" }, "vaultTimeoutLogOutConfirmation": { "message": "Η αποσύνδεση θα καταργήσει όλη την πρόσβαση στο vault σας και απαιτεί online έλεγχο ταυτότητας μετά το χρονικό όριο λήξης. Είστε βέβαιοι ότι θέλετε να χρησιμοποιήσετε αυτήν τη ρύθμιση;" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "Επιβεβαίωση Ενέργειας Χρονικού Ορίου" + "message": "Επιβεβαίωση ενέργειας χρονικού ορίου λήξης" }, "enterpriseSingleSignOn": { "message": "Ενιαία είσοδος για επιχειρήσεις" }, "setMasterPassword": { - "message": "Ορισμός Κύριου Κωδικού" + "message": "Ορισμός κύριου κωδικού πρόσβασης" }, "orgPermissionsUpdatedMustSetPassword": { "message": "Τα δικαιώματα του οργανισμού σας ενημερώθηκαν, απαιτώντας από εσάς να ορίσετε έναν κύριο κωδικό πρόσβασης.", @@ -1640,13 +1643,13 @@ "description": "Default title for the user verification dialog." }, "currentMasterPass": { - "message": "Τρέχων κύριος κωδικός" + "message": "Τρέχων κύριος κωδικός πρόσβασης" }, "newMasterPass": { - "message": "Νέος Κύριος Κωδικός" + "message": "Νέος κύριος κωδικός πρόσβασης" }, "confirmNewMasterPass": { - "message": "Επιβεβαίωση Νέου Κύριου Κωδικού" + "message": "Επιβεβαίωση νέου κύριου κωδικού πρόσβασης" }, "masterPasswordPolicyInEffect": { "message": "Σε μία ή περισσότερες πολιτικές του οργανισμού απαιτείται ο κύριος κωδικός να πληρεί τις ακόλουθες απαιτήσεις:" @@ -1691,19 +1694,19 @@ "message": "Ο νέος κύριος κωδικός δεν πληροί τις απαιτήσεις πολιτικής." }, "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "message": "Αποκτήστε μηνύματα ηλεκτρονικού ταχυδρομείου από το Bitwarden για ανακοινώσεις, συμβουλές και ερευνητικές ευκαιρίες." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "Ακύρωση συνδρομής" }, "atAnyTime": { - "message": "at any time." + "message": "οποιαδήποτε στιγμή." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Συνεχίζοντας, συμφωνείτε με" }, "and": { - "message": "and" + "message": "και" }, "acceptPolicies": { "message": "Επιλέγοντας αυτό το πλαίσιο, συμφωνείτε με τα εξής:" @@ -1712,31 +1715,31 @@ "message": "Οι Όροι Παροχής Υπηρεσιών και η Πολιτική Απορρήτου δεν έχουν αναγνωριστεί." }, "enableBrowserIntegration": { - "message": "Ενεργοποίηση ενσωμάτωσης περιηγητή" + "message": "Να επιτρέπεται η ενσωμάτωση περιηγητή" }, "enableBrowserIntegrationDesc": { - "message": "Η ενσωμάτωση του προγράμματος περιήγησης χρησιμοποιείται για βιομετρικά στοιχεία στο πρόγραμμα περιήγησης." + "message": "Χρησιμοποιείται για βιομετρικά στοιχεία στον περιηγητή." }, "enableDuckDuckGoBrowserIntegration": { - "message": "Επίτρεψε ολοκλήρωση περιηγητή DuckDuckGo" + "message": "Επίτρεψε ενσωμάτωση περιηγητή DuckDuckGo" }, "enableDuckDuckGoBrowserIntegrationDesc": { - "message": "Χρησιμοποιήστε το θησαυ/κιο Bitwarden κατά την περιήγηση με το DuckDuckGo." + "message": "Χρησιμοποιήστε το θησαυ/κιο Bitwarden σας κατά την περιήγηση με το DuckDuckGo." }, "browserIntegrationUnsupportedTitle": { "message": "Η ενσωμάτωση του περιηγητή δεν υποστηρίζεται" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "Σφάλμα ενεργοποίησης ενσωμάτωσης περιηγητή" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "Παρουσιάστηκε σφάλμα κατά την ενεργοποίηση ενσωμάτωσης του περιηγητή." }, "browserIntegrationMasOnlyDesc": { "message": "Δυστυχώς η ενσωμάτωση του προγράμματος περιήγησης υποστηρίζεται μόνο στην έκδοση Mac App Store για τώρα." }, "browserIntegrationWindowsStoreDesc": { - "message": "Δυστυχώς η ενσωμάτωση του προγράμματος περιήγησης, δεν υποστηρίζεται στην έκδοση Windows Store." + "message": "Δυστυχώς η ενσωμάτωση του περιηγητή, δεν υποστηρίζεται προς το παρόν στην έκδοση Windows Store." }, "browserIntegrationLinuxDesc": { "message": "Δυστυχώς, η ενσωμάτωση του browser δεν υποστηρίζεται αυτήν τη στιγμή στην έκδοση linux." @@ -1745,13 +1748,13 @@ "message": "Απαιτείται επαλήθευση για ολοκλήρωση περιηγητή" }, "enableBrowserIntegrationFingerprintDesc": { - "message": "Ενεργοποιήστε ένα πρόσθετο επίπεδο ασφάλειας απαιτώντας επικύρωση φράσης δακτυλικών αποτυπωμάτων κατά τη δημιουργία μιας σύνδεσης μεταξύ της επιφάνειας εργασίας σας και του προγράμματος περιήγησης. Όταν ενεργοποιηθεί, αυτό απαιτεί παρέμβαση χρήστη και επαλήθευση κάθε φορά που δημιουργείται σύνδεση." + "message": "Προσθέστε ένα πρόσθετο επίπεδο ασφάλειας απαιτώντας επιβεβαίωση φράσης δακτυλικών αποτυπωμάτων κατά τη δημιουργία μιας σύνδεσης μεταξύ της επιφάνειας εργασίας σας και του περιηγητή σας. Αυτό απαιτεί ενέργεια χρήστη και επαλήθευση κάθε φορά που δημιουργείται μια σύνδεση." }, "enableHardwareAcceleration": { "message": "Χρήση επιτάχυνσης υλικού" }, "enableHardwareAccelerationDesc": { - "message": "Εξ ορισμού αυτή η ρύθμιση είναι ΕΝΕΡΓΗ. Απενεργοποιήστε μόνο αν αντιμετωπίζετε γραφικά προβλήματα. Απαιτείται επανεκκίνηση." + "message": "Εξ ορισμού αυτή η ρύθμιση είναι ΕΝΕΡΓΉ. Αλλάξτε σε ΑΝΕΝΕΡΓΉ μόνο αν αντιμετωπίζετε γραφικά προβλήματα. Απαιτείται επανεκκίνηση." }, "approve": { "message": "Έγκριση" @@ -1772,19 +1775,19 @@ } }, "verifyNativeMessagingConnectionDesc": { - "message": "Θα θέλατε να εγκρίνετε αυτό το αίτημα?" + "message": "Θα θέλατε να εγκρίνετε αυτό το αίτημα;" }, "verifyNativeMessagingConnectionWarning": { "message": "Εάν δεν εκκινήσατε αυτό το αίτημα, μην το εγκρίνετε." }, "biometricsNotEnabledTitle": { - "message": "Η βιομετρική δεν είναι ενεργοποιημένη" + "message": "Τα βιομετρικά στοιχεία δεν είναι ενεργοποιημένα" }, "biometricsNotEnabledDesc": { - "message": "Η βιομετρική περιήγηση απαιτεί την ενεργοποίηση των βιομετρικών στοιχείων επιφάνειας εργασίας στις ρυθμίσεις πρώτα." + "message": "Τα βιομετρικά στον περιηγητή απαιτούν την ενεργοποίηση των βιομετρικών επιφάνειας εργασίας στις ρυθμίσεις πρώτα." }, "personalOwnershipSubmitError": { - "message": "Λόγω μιας Πολιτικής Επιχειρήσεων, δεν επιτρέπεται η αποθήκευση στοιχείων στο προσωπικό σας vault. Αλλάξτε την επιλογή Ιδιοκτησίας σε έναν οργανισμό και επιλέξτε από τις διαθέσιμες Συλλογές." + "message": "Λόγω μιας επιχειρηματικής πολιτικής, περιορίζεστε από την αποθήκευση αντικειμένων στο ατομικό σας θησαυ/κιό. Αλλάξτε την επιλογή ιδιοκτησίας σε έναν οργανισμό και επιλέξτε από τις διαθέσιμες συλλογές." }, "hintEqualsPassword": { "message": "Η υπόδειξη κωδικού πρόσβασης, δεν μπορεί να είναι η ίδια με τον κωδικό πρόσβασης σας." @@ -1793,7 +1796,7 @@ "message": "Μια πολιτική του οργανισμού, επηρεάζει τις επιλογές ιδιοκτησίας σας." }, "personalOwnershipPolicyInEffectImports": { - "message": "Μια πολιτική οργανισμού έχει αποτρέψει την εισαγωγή στοιχείων στην προσωπική κρύπτη σας." + "message": "Μια πολιτική οργανισμού έχει αποτρέψει την εισαγωγή αντικειμένων στο ατομικό σας θησαυ/κιο." }, "allSends": { "message": "Όλα τα Sends", @@ -1814,7 +1817,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "myVault": { - "message": "Το Vault μου" + "message": "Το θησαυ/κιό μου" }, "text": { "message": "Κείμενο" @@ -1827,14 +1830,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { - "message": "Ημερομηνία Λήξης" + "message": "Ημερομηνία λήξης" }, "expirationDateDesc": { "message": "Εάν οριστεί, η πρόσβαση σε αυτό το Send θα λήξει την καθορισμένη ημερομηνία και ώρα.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCount": { - "message": "Μέγιστος Αριθμός Πρόσβασης", + "message": "Μέγιστος αριθμός πρόσβασης", "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "maxAccessCountDesc": { @@ -1842,7 +1845,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "currentAccessCount": { - "message": "Τρέχων Αριθμός Πρόσβασης" + "message": "Τρέχων αριθμός πρόσβασης" }, "disableSend": { "message": "Απενεργοποιήστε αυτό το Send έτσι ώστε κανείς να μην μπορεί να έχει πρόσβαση σε αυτό.", @@ -1861,7 +1864,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinkLabel": { - "message": "Αποστολή Συνδέσμου", + "message": "Σύνδεσμος Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "textHiddenByDefault": { @@ -1869,26 +1872,26 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Το Send Δημιουργήθηκε", + "message": "Το Send προστέθηκε", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Το Send Επεξεργάστηκε", + "message": "Το Send αποθηκεύτηκε", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { - "message": "Το Send Διαγράφηκε", + "message": "Το Send διαγράφηκε", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { - "message": "Νέος Κωδικός" + "message": "Νέος κωδικός πρόσβασης" }, "whatTypeOfSend": { "message": "Τι είδους Send είναι αυτό;", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "Δημιουργήστε Send", + "message": "Νέο Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { @@ -1924,7 +1927,7 @@ "message": "Αντιγράψτε το σύνδεσμο, για να μοιραστείτε αυτό το Send στο πρόχειρο μου, κατά την αποθήκευση." }, "sendDisabled": { - "message": "Send Απενεργοποιημένο", + "message": "Το Send αφαιρέθηκε", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -1938,13 +1941,13 @@ "message": "Απενεργοποιημένο" }, "removePassword": { - "message": "Κατάργηση κωδικού πρόσβασης" + "message": "Αφαίρεση κωδικού πρόσβασης" }, "removedPassword": { - "message": "Ο κωδικός πρόσβασης καταργήθηκε" + "message": "Ο κωδικός πρόσβασης αφαιρέθηκε" }, "removePasswordConfirmation": { - "message": "Είστε σίγουροι ότι θέλετε να καταργήσετε τον κωδικό πρόσβασης;" + "message": "Είστε σίγουροι ότι θέλετε να αφαιρέσετε τον κωδικό πρόσβασης;" }, "maxAccessCountReached": { "message": "Φτάσατε στον μέγιστο αριθμό πρόσβασης" @@ -1965,10 +1968,10 @@ "message": "Μία ή περισσότερες οργανωτικές πολιτικές επηρεάζουν τις επιλογές send σας." }, "emailVerificationRequired": { - "message": "Απαιτείται Επαλήθευση Email" + "message": "Απαιτείται επαλήθευση ηλεκτρονικού ταχυδρομείου" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "Email επιβεβαιωμένο" }, "emailVerificationRequiredDesc": { "message": "Πρέπει να επαληθεύσετε το email σας για να χρησιμοποιήσετε αυτή τη δυνατότητα." @@ -1989,10 +1992,10 @@ "message": "Ενημερώστε τον κύριο κωδικό πρόσβασης" }, "updateMasterPasswordWarning": { - "message": "Ο Κύριος Κωδικός Πρόσβασής σας άλλαξε πρόσφατα από διαχειριστή στον οργανισμό σας. Για να αποκτήσετε πρόσβαση στο vault, πρέπει να τον ενημερώσετε τώρα. Η διαδικασία θα σας αποσυνδέσει από την τρέχουσα συνεδρία σας, απαιτώντας από εσάς να συνδεθείτε ξανά. Οι ενεργές συνεδρίες σε άλλες συσκευές ενδέχεται να συνεχίσουν να είναι ενεργές για μία ώρα." + "message": "Ο κύριος κωδικός πρόσβασής σας άλλαξε πρόσφατα από έναν διαχειριστή στον οργανισμό σας. Για να αποκτήσετε πρόσβαση στο θησαυ/κιο, πρέπει να τον ενημερώσετε τώρα. Η διαδικασία θα σας αποσυνδέσει από την τρέχουσα συνεδρία σας, απαιτώντας από εσάς να συνδεθείτε ξανά. Οι ενεργές συνεδρίες σε άλλες συσκευές ενδέχεται να συνεχίσουν να είναι ενεργές για μία ώρα." }, "updateWeakMasterPasswordWarning": { - "message": "Ο κύριος κωδικός πρόσβασης δεν πληροί τις απαιτήσεις πολιτικής αυτού του οργανισμού. Για να έχετε πρόσβαση στην κρύπτη, πρέπει να ενημερώσετε τον κύριο κωδικό σας άμεσα. Η διαδικασία θα σάς αποσυνδέσει από την τρέχουσα συνεδρία σας, απαιτώντας να συνδεθείτε ξανά. Οι ενεργές συνεδρίες σε άλλες συσκευές ενδέχεται να συνεχίσουν να είναι ενεργές για το πολύ μία ώρα." + "message": "Ο κύριος κωδικός πρόσβασής σας δεν πληροί μία ή περισσότερες πολιτικές του οργανισμού σας. Για να αποκτήσετε πρόσβαση στο θησαυ/κιο, πρέπει να ενημερώσετε τον κύριο κωδικό πρόσβασής σας τώρα. Η διαδικασία θα σας αποσυνδέσει από την τρέχουσα συνεδρία σας, απαιτώντας από εσάς να συνδεθείτε ξανά. Οι ενεργές συνεδρίες σε άλλες συσκευές ενδέχεται να συνεχίσουν να είναι ενεργές για μία ώρα." }, "tryAgain": { "message": "Προσπαθήστε ξανά" @@ -2016,7 +2019,7 @@ "message": "Χρειάζεστε μια διαφορετική μέθοδο;" }, "useMasterPassword": { - "message": "Χρήση κύριου κωδικού" + "message": "Χρήση κύριου κωδικού πρόσβασης" }, "usePin": { "message": "Χρήση PIN" @@ -2025,7 +2028,7 @@ "message": "Χρήση βιομετρικών" }, "enterVerificationCodeSentToEmail": { - "message": "Εισάγετε τον κωδικό επαλήθευσης που έχει σταλεί στο email σας." + "message": "Εισάγετε τον κωδικό επαλήθευσης που έχει σταλεί στο ηλεκτρονικό ταχυδρομείο σας." }, "resendCode": { "message": "Επαναποστολή κωδικού" @@ -2037,7 +2040,7 @@ "message": "Λεπτά" }, "vaultTimeoutPolicyInEffect": { - "message": "Οι πολιτικές του οργανισμού σας επηρεάζουν το χρονικό όριο vault σας. Το μέγιστο επιτρεπόμενο Χρονικό όριο Vault είναι $HOURS$ ώρα(ες) και $MINUTES$ λεπτό(ά)", + "message": "Οι πολιτικές του οργανισμού σας έχουν ορίσει το μέγιστο επιτρεπόμενο χρονικό όριο λήξης θησαυ/κίου σε $HOURS$ ώρα(ες) και $MINUTES$ λεπτό(α).", "placeholders": { "hours": { "content": "$1", @@ -2050,7 +2053,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "Οι πολιτικές του οργανισμού σας επηρεάζουν το χρονικό όριο λήξης της κρύ[της σας. Το μέγιστο επιτρεπόμενο χρονικό όριο λήξης vault είναι $HOURS$ ώρα(ες) και $MINUTES$ λεπτό(ά). H ενέργεια χρονικού ορίου λήξης της κρύπτης είναι ορισμένη ως $ACTION$.", + "message": "Οι πολιτικές του οργανισμού σας επηρεάζουν το χρονικό όριο λήξης του θησαυ/κίου σας. Το μέγιστο επιτρεπόμενο χρονικό όριο θησαυ/κίου είναι $HOURS$ ώρα(ες) και $MINUTES$ λεπτό(α). Η ενέργεια χρονικού ορίου λήξης του θησαυ/κίου σας έχει οριστεί σε $ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -2067,7 +2070,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "Οι πολιτικές του οργανισμού σας έχουν ορίσει την ενέργεια χρονικού ορίου λήξης κρύπτης σε $ACTION$.", + "message": "Οι πολιτικές του οργανισμού σας έχουν ορίσει την ενέργεια χρονικού ορίου λήξης του θησαυ/κίου σας σε $ACTION$.", "placeholders": { "action": { "content": "$1", @@ -2079,25 +2082,25 @@ "message": "Το χρονικό όριο του vault σας υπερβαίνει τους περιορισμούς που έχει ορίσει ο οργανισμός σας." }, "resetPasswordPolicyAutoEnroll": { - "message": "Αυτόματη Εγγραφή" + "message": "Αυτόματη εγγραφή" }, "resetPasswordAutoEnrollInviteWarning": { "message": "Αυτός ο οργανισμός έχει μια επιχειρηματική πολιτική που θα σας εγγράψει αυτόματα στην επαναφορά κωδικού. Η εγγραφή θα επιτρέψει στους διαχειριστές του οργανισμού να αλλάξουν τον κύριο κωδικό πρόσβασης σας." }, "vaultExportDisabled": { - "message": "Εξαγωγή vault Απενεργοποιημένη" + "message": "Αφαίρεση εξαγωγής θησαυ/κίου" }, "personalVaultExportPolicyInEffect": { "message": "Μία ή περισσότερες οργανωτικές πολιτικές σας αποτρέπει από την εξαγωγή του προσωπικού vault." }, "addAccount": { - "message": "Προσθήκη Λογαριασμού" + "message": "Προσθήκη λογαριασμού" }, "removeMasterPassword": { - "message": "Αφαίρεση Κύριου Κωδικού Πρόσβασης" + "message": "Αφαίρεση κύριου κωδικού πρόσβασης" }, "removedMasterPassword": { - "message": "Ο κύριος κωδικός αφαιρέθηκε." + "message": "Ο κύριος κωδικός αφαιρέθηκε" }, "convertOrganizationEncryptionDesc": { "message": "$ORGANIZATION$ χρησιμοποιεί SSO με έναν αυτοεξυπηρετητή κλειδιών. Ένας κύριος κωδικός πρόσβασης δεν απαιτείται πλέον για να συνδεθείτε για τα μέλη αυτού του οργανισμού.", @@ -2118,10 +2121,10 @@ "message": "Έχετε φύγει από τον οργανισμό." }, "ssoKeyConnectorError": { - "message": "Σφάλμα Key Connector: βεβαιωθείτε ότι το Key Connector είναι διαθέσιμο και λειτουργεί σωστά." + "message": "Σφάλμα Key connector: βεβαιωθείτε ότι το Key connector είναι διαθέσιμο και λειτουργεί σωστά." }, "lockAllVaults": { - "message": "Κλείδωμα Όλων Των Vault" + "message": "Κλείδωμα όλων των θησαυ/κίων" }, "accountLimitReached": { "message": "Δεν μπορούν να συνδεθούν περισσότεροι από 5 λογαριασμοί ταυτόχρονα." @@ -2130,7 +2133,7 @@ "message": "Προτιμήσεις" }, "appPreferences": { - "message": "Ρυθμίσεις Εφαρμογής (Όλοι Οι Λογαριασμοί)" + "message": "Ρυθμίσεις εφαρμογής (όλοι οι λογαριασμοί)" }, "accountSwitcherLimitReached": { "message": "Συμπληρώθηκε το όριο λογαριασμού. Αποσυνδεθείτε από έναν λογαριασμό για να προσθέσετε έναν άλλο." @@ -2145,10 +2148,10 @@ } }, "switchAccount": { - "message": "Εναλλαγή λογαριασμού" + "message": "Αλλαγή λογαριασμού" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Έχετε ήδη λογαριασμό;" }, "options": { "message": "Ρυθμίσεις" @@ -2157,10 +2160,10 @@ "message": "Έχει λήξει το χρονικό όριο. Παρακαλώ επιστρέψτε και προσπαθήστε να συνδεθείτε ξανά." }, "exportingPersonalVaultTitle": { - "message": "Εξαγωγή Προσωπικού Vault" + "message": "Εξαγωγή ατομικού θησαυ/κίου" }, "exportingIndividualVaultDescription": { - "message": "Μόνο τα μεμονωμένα αντικείμενα κρύπτης που σχετίζονται με το $EMAIL$ θα εξαχθούν. Τα αντικείμενα κρύπτης οργανισμού δε θα συμπεριληφθούν. Μόνο πληροφορίες αντικειμένων κρύπτης θα εξαχθούν και δε θα περιλαμβάνουν συσχετιζόμενα συνημμένα.", + "message": "Μόνο τα ατομικά αντικείμενα θησαυ/κίου που σχετίζονται με το $EMAIL$ θα εξαχθούν. Τα αντικείμενα θησαυ/κίου του οργανισμού δε θα συμπεριληφθούν. Μόνο πληροφορίες αντικειμένων θησαυ/κίου θα εξαχθούν και δε θα περιλαμβάνουν συσχετιζόμενα συνημμένα.", "placeholders": { "email": { "content": "$1", @@ -2169,10 +2172,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "Εξαγωγή θησαυ/κίου οργανισμού" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "Μόνο το θησαυ/κιο του οργανισμού που σχετίζεται με το $ORGANIZATION$ θα εξαχθεί. Αντικείμενα σε ατομικά θησαυ/κια ή άλλους οργανισμούς δε θα συμπεριληφθούν.", "placeholders": { "organization": { "content": "$1", @@ -2193,26 +2196,26 @@ "message": "Τι θα θέλατε να δημιουργήσετε;" }, "passwordType": { - "message": "Τύπος Κωδικού" + "message": "Τύπος κωδικού πρόσβασης" }, "regenerateUsername": { - "message": "Επαναδημιουργία Ονόματος Χρήστη" + "message": "Επαναδημιουργία ονόματος χρήστη" }, "generateUsername": { - "message": "Δημιουργία Ονόματος Χρήστη" + "message": "Δημιουργία ονόματος χρήστη" }, "usernameType": { - "message": "Τύπος Ονόματος Χρήστη" + "message": "Τύπος ονόματος χρήστη" }, "plusAddressedEmail": { - "message": "Συν Διεύθυνση Email", + "message": "Συν διεύθυνση ηλεκτρονικού ταχυδρομείου", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { "message": "Χρησιμοποιήστε τις δυνατότητες δευτερεύουσας διεύθυνσης του παρόχου email σας." }, "catchallEmail": { - "message": "Catch-all Email" + "message": "Ηλεκτρονικό ταχυδρομείο για κάθε σκοπό" }, "catchallEmailDesc": { "message": "Χρησιμοποιήστε τα διαμορφωμένα εισερχόμενα catch-all του domain σας." @@ -2221,31 +2224,31 @@ "message": "Τυχαίο" }, "randomWord": { - "message": "Τυχαία Λέξη" + "message": "Τυχαία λέξη" }, "websiteName": { - "message": "Όνομα Ιστοσελίδας" + "message": "Όνομα ιστοσελίδας" }, "service": { "message": "Υπηρεσία" }, "allVaults": { - "message": "Όλα τα Vaults" + "message": "Όλα τα θησαυ/κια" }, "searchOrganization": { - "message": "Αναζήτηση Οργανισμού" + "message": "Αναζήτηση οργανισμού" }, "searchMyVault": { - "message": "Αναζήτηση στο Vault" + "message": "Αναζήτηση στο θησαυ/κιό μου" }, "forwardedEmail": { - "message": "Προωθημένο Email Alias" + "message": "Προωθημένο ψευδώνυμο ηλεκτρονικού ταχυδρομείου" }, "forwardedEmailDesc": { "message": "Δημιουργήστε ένα alias email με μια εξωτερική υπηρεσία προώθησης." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ σφάλμα: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2259,11 +2262,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Δημιουργήθηκε από το Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Ιστοσελίδα: $WEBSITE$. Δημιουργήθηκε από το Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2273,7 +2276,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Μη έγκυρο $SERVICENAME$ διακριτικό API", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2283,7 +2286,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Μη έγκυρο $SERVICENAME$ API διακριτικό: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2297,7 +2300,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "Αδύνατη η απόκτηση του $SERVICENAME$ καμουφλαρισμένου email ID.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2307,7 +2310,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Μη έγκυρος $SERVICENAME$ τομέας.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2317,7 +2320,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "Μη έγκυρο $SERVICENAME$ url.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2327,7 +2330,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Παρουσιάστηκε άγνωστο $SERVICENAME$ σφάλμα.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2337,7 +2340,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Άγνωστος διαβιβαστής: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2363,28 +2366,28 @@ "message": "Ο οργανισμός διακόπηκε" }, "disabledOrganizationFilterError": { - "message": "Δεν είναι δυνατή η πρόσβαση σε ανασταλμένους οργανισμούς. Επικοινωνήστε με τον ιδιοκτήτη του οργανισμού σας για βοήθεια." + "message": "Δεν είναι δυνατή η πρόσβαση αντικειμένων σε ανασταλμένους οργανισμούς. Επικοινωνήστε με τον ιδιοκτήτη του οργανισμού σας για βοήθεια." }, "neverLockWarning": { - "message": "Είστε βέβαιοι ότι θέλετε να χρησιμοποιήσετε την επιλογή \"Ποτέ\"; Ο ορισμός των επιλογών κλειδώματος σε \"Ποτέ\" αποθηκεύει το κλειδί κρυπτογράφησης του vault στη συσκευή σας. Εάν χρησιμοποιήσετε αυτήν την επιλογή, θα πρέπει να διασφαλίσετε ότι θα διατηρείτε τη συσκευή σας σωστά προστατευμένη." + "message": "Είστε βέβαιοι ότι θέλετε να χρησιμοποιήσετε την επιλογή \"Ποτέ\"; Ο ορισμός των επιλογών κλειδώματος σε \"Ποτέ\" αποθηκεύει το κλειδί κρυπτογράφησης του θησαυ/κίου σας στη συσκευή σας. Εάν χρησιμοποιήσετε αυτήν την επιλογή, θα πρέπει να διασφαλίσετε ότι θα διατηρείτε τη συσκευή σας κατάλληλα προστατευμένη." }, "vault": { - "message": "Υπόγειο" + "message": "Θησαυ/κιο" }, "loginWithMasterPassword": { - "message": "Συνδεθείτε με τον κύριο κωδικό" + "message": "Συνδεθείτε με τον κύριο κωδικό πρόσβασης" }, "loggingInAs": { "message": "Σύνδεση ως" }, "rememberEmail": { - "message": "Απομνημόνευση email" + "message": "Απομνημόνευση ηλεκτρονικού ταχυδρομείου" }, "notYou": { - "message": "Όχι εσείς?" + "message": "Όχι εσείς;" }, "newAroundHere": { - "message": "Νέο εδώ?" + "message": "Είστε νέος/α εδώ;" }, "loggingInTo": { "message": "Σύνδεση στο $DOMAIN$", @@ -2405,13 +2408,13 @@ "message": "Μια ειδοποίηση έχει σταλεί στη συσκευή σας." }, "fingerprintMatchInfo": { - "message": "Βεβαιωθείτε ότι το vault σας είναι ξεκλείδωτο και ότι η Φράση δακτυλικών αποτυπωμάτων ταιριάζει στην άλλη συσκευή." + "message": "Παρακαλώ βεβαιωθείτε ότι το θησαυ/κιό σας είναι ξεκλείδωτο και η φράση δακτυλικών αποτυπωμάτων ταιριάζει με την άλλη συσκευή." }, "fingerprintPhraseHeader": { "message": "Φράση δακτυλικών αποτυπωμάτων" }, "needAnotherOption": { - "message": "Η σύνδεση με χρήση συσκευής πρέπει να ρυθμιστεί στις ρυθμίσεις της εφαρμογής Bitwarden. Χρειάζεστε άλλη επιλογή;" + "message": "Η σύνδεση με χρήση συσκευής πρέπει να παραμετροποιηθεί στις ρυθμίσεις της εφαρμογής Bitwarden. Χρειάζεστε άλλη επιλογή;" }, "viewAllLoginOptions": { "message": "Δείτε όλες τις επιλογές σύνδεσης" @@ -2420,7 +2423,7 @@ "message": "Επαναποστολή ειδοποίησης" }, "toggleCharacterCount": { - "message": "Εναλλαγή μετρήσεων χαρακτήρων", + "message": "Εναλλαγή αριθμού χαρακτήρων", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, "areYouTryingtoLogin": { @@ -2497,52 +2500,52 @@ "message": "Ζητήθηκε σύνδεση" }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Δημιουργία λογαριασμού στο" }, "checkYourEmail": { - "message": "Check your email" + "message": "Ελέγξτε το email σας" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "Ακολουθήστε το σύνδεσμο στο email που στάλθηκε στο" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "και συνεχίστε στη δημιουργία του λογαριασμού σας." }, "noEmail": { - "message": "No email?" + "message": "Όχι email;" }, "goBack": { - "message": "Go back" + "message": "Μετάβαση πίσω" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "για να επεξεργαστείτε τη διεύθυνση ηλεκτρονικού ταχυδρομείου σας." }, "exposedMasterPassword": { "message": "Εκτεθειμένος Κύριος Κωδικός Πρόσβασης" }, "exposedMasterPasswordDesc": { - "message": "Ο κωδικός βρέθηκε σε μια παραβίαση δεδομένων. Χρησιμοποιήστε έναν μοναδικό κωδικό πρόσβασης για την προστασία του λογαριασμού σας. Είστε βέβαιοι ότι θέλετε να χρησιμοποιήσετε έναν εκτεθειμένο κωδικό πρόσβασης;" + "message": "Κωδικός πρόσβασης βρέθηκε σε μια διαρροή δεδομένων. Χρησιμοποιήστε έναν μοναδικό κωδικό πρόσβασης για την προστασία του λογαριασμού σας. Είστε σίγουροι ότι θέλετε να χρησιμοποιήσετε έναν εκτεθειμένο κωδικό πρόσβασης;" }, "weakAndExposedMasterPassword": { - "message": "Αδύναμος και Εκτεθειμένος Κύριος Κωδικός" + "message": "Αδύναμος και Εκτεθειμένος Κύριος Κωδικός Πρόσβασης" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Αδύναμος κωδικός που έχει βρεθεί σε μια παραβίαση δεδομένων. Χρησιμοποιήστε ένα ισχυρό και μοναδικό κωδικό πρόσβασης για την προστασία του λογαριασμού σας. Είστε βέβαιοι ότι θέλετε να χρησιμοποιήσετε αυτόν τον κωδικό πρόσβασης;" + "message": "Βρέθηκε και ταυτοποιήθηκε αδύναμος κωδικός σε μια διαρροή δεδομένων. Χρησιμοποιήστε ένα ισχυρό και μοναδικό κωδικό πρόσβασης για την προστασία του λογαριασμού σας. Είστε σίγουροι ότι θέλετε να χρησιμοποιήσετε αυτόν τον κωδικό πρόσβασης;" }, "checkForBreaches": { - "message": "Ελέγξτε γνωστές παραβιάσεις δεδομένων για αυτόν τον κωδικό" + "message": "Ελέγξτε γνωστές διαρροές δεδομένων για αυτόν τον κωδικό" }, "important": { "message": "Σημαντικό:" }, "accessTokenUnableToBeDecrypted": { - "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + "message": "Έχετε αποσυνδεθεί επειδή το διακριτικό πρόσβασής σας δεν μπορεί να αποκρυπτογραφηθεί. Παρακαλώ συνδεθείτε ξανά για να επιλύσετε αυτό το ζήτημα." }, "refreshTokenSecureStorageRetrievalFailure": { - "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + "message": "Έχετε αποσυνδεθεί επειδή το διακριτικό ανανέωσης δεν μπορεί να ανακτηθεί. Παρακαλώ συνδεθείτε ξανά για να επιλύσετε αυτό το ζήτημα." }, "masterPasswordHint": { - "message": "Ο κύριος κωδικός πρόσβασης δεν μπορεί να ανακτηθεί εάν τον ξεχάσετε!" + "message": "Ο κύριος κωδικός πρόσβασης σας δεν μπορεί να ανακτηθεί εάν τον ξεχάσετε!" }, "characterMinimum": { "message": "Τουλάχιστον $LENGTH$ χαρακτήρες", @@ -2554,7 +2557,7 @@ } }, "windowsBiometricUpdateWarning": { - "message": "Το Bitwarden συστήνει την ενημέρωση των βιομετρικών ρυθμίσεών σας ώστε να απαιτηθεί ο κύριος κωδικός πρόσβασης (ή PIN) στο πρώτο ξεκλείδωμα. Θέλετε να ενημερώσετε τις ρυθμίσεις σας τώρα;" + "message": "Το Bitwarden συστήνει την ενημέρωση των βιομετρικών ρυθμίσεών σας ώστε να απαιτείται ο κύριος κωδικός πρόσβασης (ή PIN) στο πρώτο ξεκλείδωμα. Θέλετε να ενημερώσετε τις ρυθμίσεις σας τώρα;" }, "windowsBiometricUpdateWarningTitle": { "message": "Ενημέρωση Προτεινόμενων Ρυθμίσεων" @@ -2575,7 +2578,7 @@ "message": "Αίτηση έγκρισης διαχειριστή" }, "approveWithMasterPassword": { - "message": "Έγκριση με τον κύριο κωδικό" + "message": "Έγκριση με τον κύριο κωδικό πρόσβασης" }, "region": { "message": "Περιοχή" @@ -2615,13 +2618,13 @@ "message": "Η σύνδεση εγκρίθηκε" }, "userEmailMissing": { - "message": "Το email του χρήστη λείπει" + "message": "Το ηλεκτρονικό ταχυδρομείο του χρήστη λείπει" }, "deviceTrusted": { "message": "Αξιόπιστη συσκευή" }, "inputRequired": { - "message": "Απαιτείται είσοδος." + "message": "Απαιτείται καταχώρηση." }, "required": { "message": "απαιτείται" @@ -2630,7 +2633,7 @@ "message": "Αναζήτηση" }, "inputMinLength": { - "message": "Η είσοδος πρέπει να είναι τουλάχιστον $COUNT$ χαρακτήρες.", + "message": "Η καταχώρηση πρέπει να είναι τουλάχιστον $COUNT$ χαρακτήρες.", "placeholders": { "count": { "content": "$1", @@ -2639,7 +2642,7 @@ } }, "inputMaxLength": { - "message": "Η είσοδος δεν πρέπει να υπερβαίνει τους $COUNT$ χαρακτήρες.", + "message": "Η καταχώρηση δεν πρέπει να υπερβαίνει τους $COUNT$ χαρακτήρες.", "placeholders": { "count": { "content": "$1", @@ -2657,7 +2660,7 @@ } }, "inputMinValue": { - "message": "Η τιμή εισόδου πρέπει να είναι τουλάχιστον $MIN$.", + "message": "Η τιμή καταχώρησης πρέπει να είναι τουλάχιστον $MIN$.", "placeholders": { "min": { "content": "$1", @@ -2666,7 +2669,7 @@ } }, "inputMaxValue": { - "message": "Η τιμή εισόδου δεν πρέπει να υπερβαίνει το $MAX$.", + "message": "Η τιμή καταχώρησης δεν πρέπει να υπερβαίνει το $MAX$.", "placeholders": { "max": { "content": "$1", @@ -2675,14 +2678,14 @@ } }, "multipleInputEmails": { - "message": "1 ή περισσότερα email δεν είναι έγκυρα" + "message": "1 ή περισσότερα ηλεκτρονικά ταχυδρομεία δεν είναι έγκυρα" }, "inputTrimValidator": { - "message": "Η είσοδος δεν πρέπει να περιέχει μόνο κενά.", + "message": "Η καταχώρηση δεν πρέπει να περιέχει μόνο κενά.", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { - "message": "Η είσοδος δεν είναι διεύθυνση email." + "message": "Η καταχώρηση δεν είναι διεύθυνση ηλεκτρονικού ταχυδρομείου." }, "fieldsNeedAttention": { "message": "$COUNT$ πεδίο(α) παραπάνω χρειάζονται την προσοχή σας.", @@ -2721,7 +2724,7 @@ "message": "Υπομενού" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "Εναλλαγή πλευρικής πλοήγησης" }, "skipToContent": { "message": "Μετάβαση στο περιεχόμενο" @@ -2730,10 +2733,10 @@ "message": "Κλειδί πρόσβασης" }, "passkeyNotCopied": { - "message": "Το κλειδί πρόσβασης δεν θα αντιγραφεί" + "message": "Το κλειδί πρόσβασης δε θα αντιγραφεί" }, "passkeyNotCopiedAlert": { - "message": "Το κλειδί πρόσβασης δε θα αντιγραφεί στο κλωνοποιημένο στοιχείο. Θέλετε να συνεχίσετε την κλωνοποίηση αυτού του στοιχείου;" + "message": "Το κλειδί πρόσβασης δε θα αντιγραφεί στο κλωνοποιημένο αντικείμενο. Θέλετε να συνεχίσετε την κλωνοποίηση αυτού του αντικειμένου;" }, "aliasDomain": { "message": "Ψευδώνυμο τομέα" @@ -2746,7 +2749,7 @@ "message": "Σφάλμα κατά την εισαγωγή" }, "importErrorDesc": { - "message": "Παρουσιάστηκε πρόβλημα με τα δεδομένα που επιχειρήσατε να εισαγάγετε. Παρακαλώ επιλύστε τα σφάλματα που αναφέρονται παρακάτω στο αρχείο πηγής και προσπαθήστε ξανά." + "message": "Παρουσιάστηκε πρόβλημα με τα δεδομένα που επιχειρήσατε να εισαγάγετε. Παρακαλώ επιλύστε τα σφάλματα που αναφέρονται παρακάτω στο πηγαίο αρχείο και προσπαθήστε ξανά." }, "resolveTheErrorsBelowAndTryAgain": { "message": "Επιλύστε τα παρακάτω σφάλματα και προσπαθήστε ξανά." @@ -2770,7 +2773,7 @@ "message": "Σύνολο" }, "importWarning": { - "message": "Εισαγάγετε δεδομένα στην $ORGANIZATION$. Τα δεδομένα σας μπορεί να μοιραστούν με μέλη αυτού του οργανισμού. Θέλετε να συνεχίσετε;", + "message": "Εισαγάγετε δεδομένα στο $ORGANIZATION$. Τα δεδομένα σας μπορεί να μοιραστούν με μέλη αυτού του οργανισμού. Θέλετε να συνεχίσετε;", "placeholders": { "organization": { "content": "$1", @@ -2778,23 +2781,26 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Σφάλμα κατά τη σύνδεση με την υπηρεσία Duo. Χρησιμοποιήστε μια διαφορετική μέθοδο σύνδεσης δύο βημάτων ή επικοινωνήστε με την Duo για βοήθεια." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Εκκινήστε το Duo και ακολουθήστε τα βήματα για να ολοκληρώσετε τη σύνδεση." }, "duoRequiredByOrgForAccount": { - "message": "Η Σύνδεση δύο βημάτων Duo απαιτείται για τον λογαριασμό σας." + "message": "Η σύνδεση δύο βημάτων Duo απαιτείται για τον λογαριασμό σας." }, "launchDuo": { "message": "Εκκίνηση Duo στον περιηγητή" }, "importFormatError": { - "message": "Τα δεδομένα δεν έχουν διαμορφωθεί σωστά. Ελέγξτε το αρχείο εισαγωγής και δοκιμάστε ξανά." + "message": "Τα δεδομένα δεν είναι σωστά διαμορφωμένα. Παρακαλώ ελέγξτε το αρχείο εισαγωγής σας και δοκιμάστε ξανά." }, "importNothingError": { "message": "Τίποτα δεν εισήχθη." }, "importEncKeyError": { - "message": "Σφάλμα αποκρυπτογράφησης του εξαγόμενου αρχείου. Το κλειδί κρυπτογράφησης δεν ταιριάζει με το κλειδί κρυπτογράφησης που χρησιμοποιήθηκε για την εξαγωγή των δεδομένων." + "message": "Σφάλμα αποκρυπτογράφησης του εξαγόμενου αρχείου. Το κλειδί κρυπτογράφησης σας δεν ταιριάζει με το κλειδί κρυπτογράφησης που χρησιμοποιήθηκε για την εξαγωγή των δεδομένων." }, "invalidFilePassword": { "message": "Μη έγκυρος κωδικός πρόσβασης, παρακαλώ χρησιμοποιήστε τον κωδικό πρόσβασης που εισαγάγατε όταν δημιουργήσατε το αρχείο εξαγωγής." @@ -2812,7 +2818,7 @@ "message": "Επιλέξτε μια συλλογή" }, "importTargetHint": { - "message": "Επιλέξτε αυτό αν θέλετε τα περιεχόμενα του εισαγόμενου αρχείου να μετακινηθούν σε $DESTINATION$", + "message": "Επιλέξτε αυτή την επιλογή εάν θέλετε τα περιεχόμενα του εισαγόμενου αρχείου να μετακινηθούν σε $DESTINATION$", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -2822,10 +2828,10 @@ } }, "importUnassignedItemsError": { - "message": "Το αρχείο περιέχει μη συσχετισμένα στοιχεία." + "message": "Το αρχείο περιέχει μη αναθετημένα αντικείμενα." }, "selectFormat": { - "message": "Επιλέξτε τη μορφή του αρχείου εισαγωγής" + "message": "Επιλέξτε τον τύπο του αρχείου εισαγωγής" }, "selectImportFile": { "message": "Επιλέξτε το αρχείο εισαγωγής" @@ -2850,7 +2856,7 @@ } }, "confirmVaultImport": { - "message": "Επιβεβαίωση εισαγωγής κρύπτης" + "message": "Επιβεβαίωση εισαγωγής θησαυ/κίου" }, "confirmVaultImportDesc": { "message": "Αυτό το αρχείο προστατεύεται με κωδικό πρόσβασης. Παρακαλώ εισαγάγετε τον κωδικό πρόσβασης για την εισαγωγή δεδομένων." @@ -2859,19 +2865,19 @@ "message": "Επιβεβαίωση κωδικού πρόσβασης αρχείου" }, "exportSuccess": { - "message": "Vault data exported" + "message": "Εξήχθησαν τα δεδομένα θησαυ/κίου" }, "multifactorAuthenticationCancelled": { - "message": "Ο πολυμερής έλεγχος ταυτότητας ακυρώθηκε" + "message": "Η αυθεντικοποίηση πολλών παραγόντων ακυρώθηκε" }, "noLastPassDataFound": { - "message": "Δεν βρέθηκαν δεδομένα LastPass" + "message": "Δε βρέθηκαν δεδομένα LastPass" }, "incorrectUsernameOrPassword": { - "message": "Λάθος όνομα χρήστη ή κωδικού πρόσβασης" + "message": "Λάθος όνομα χρήστη ή κωδικός πρόσβασης" }, "incorrectPassword": { - "message": "Λάθος κωδικός" + "message": "Λάθος κωδικός πρόσβασης" }, "incorrectCode": { "message": "Λάθος κωδικός" @@ -2880,25 +2886,25 @@ "message": "Λάθος PIN" }, "multifactorAuthenticationFailed": { - "message": "Ο πολυμερής έλεγχος ταυτότητας απέτυχε" + "message": "Η αυθεντικοποίηση πολλών παραγόντων απέτυχε" }, "includeSharedFolders": { "message": "Συμπερίληψη κοινόχρηστων φακέλων" }, "lastPassEmail": { - "message": "LastPass Email" + "message": "Διεύθυνση ηλεκτρονικού ταχυδρομείου LastPass" }, "importingYourAccount": { "message": "Εισαγωγή του λογαριασμού σας..." }, "lastPassMFARequired": { - "message": "Απαιτείται πολυμερής ταυτοποίηση LastPass" + "message": "Απαιτείται αυθεντικοποίηση πολλών παραγόντων LastPass" }, "lastPassMFADesc": { - "message": "Εισαγάγετε τον κωδικό μιας χρήσης από την εφαρμογή επαλήθευσης" + "message": "Εισαγάγετε τον κωδικό μιας χρήσης από την εφαρμογή αυθεντικοποίησης" }, "lastPassOOBDesc": { - "message": "Εγκρίνετε το αίτημα σύνδεσης στην εφαρμογή επαλήθευσης ή εισαγάγετε έναν κωδικό πρόσβασης μιας χρήσης." + "message": "Εγκρίνετε το αίτημα σύνδεσης στην εφαρμογή αυθεντικοποίησής σας ή εισαγάγετε έναν κωδικό πρόσβασης μιας χρήσης." }, "passcode": { "message": "Κωδικός" @@ -2907,16 +2913,16 @@ "message": "Κύριος κωδικός πρόσβασης LastPass" }, "lastPassAuthRequired": { - "message": "Απαιτείται ταυτοποίηση LastPass" + "message": "Απαιτείται αυθεντικοποίηση LastPass" }, "awaitingSSO": { - "message": "Αναμονή ελέγχου ταυτότητας SSO" + "message": "Αναμονή αυθεντικοποίησης SSO" }, "awaitingSSODesc": { "message": "Παρακαλούμε συνεχίστε τη σύνδεση χρησιμοποιώντας τα στοιχεία της εταιρείας σας." }, "seeDetailedInstructions": { - "message": "Δείτε λεπτομερείς οδηγίες στην ιστοσελίδα βοήθειας μας στο", + "message": "Δείτε λεπτομερείς οδηγίες στη βοηθητική ιστοσελίδα μας στο", "description": "This is followed a by a hyperlink to the help website." }, "importDirectlyFromLastPass": { @@ -2926,23 +2932,23 @@ "message": "Εισαγωγή από CSV" }, "lastPassTryAgainCheckEmail": { - "message": "Δοκιμάστε ξανά ή ψάξτε για ένα email από το LastPass για να επιβεβαιώσετε ότι είστε εσείς." + "message": "Δοκιμάστε ξανά ή ψάξτε για ένα μήνυμα ηλεκτρονικού ταχυδρομείου από το LastPass για να επιβεβαιώσετε ότι είστε εσείς." }, "collection": { "message": "Συλλογή" }, "lastPassYubikeyDesc": { - "message": "Εισαγάγετε το YubiKey που σχετίζεται με το λογαριασμό LastPass στη θύρα USB του υπολογιστή σας και στη συνέχεια αγγίξτε το κουμπί του." + "message": "Εισάγετε το YubiKey που σχετίζεται με τον λογαριασμό LastPass στη θύρα USB του υπολογιστή σας και στη συνέχεια αγγίξτε το κουμπί του." }, "commonImportFormats": { - "message": "Κοινές μορφές", + "message": "Κοινοί τύποι", "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "Επιτυχία" }, "troubleshooting": { - "message": "Αντιμετώπιση Προβλημάτων" + "message": "Αντιμετώπιση προβλημάτων" }, "disableHardwareAccelerationRestart": { "message": "Απενεργοποίηση επιτάχυνσης υλικού και επανεκκίνηση" @@ -2951,19 +2957,19 @@ "message": "Ενεργοποίηση επιτάχυνσης υλικού και επανεκκίνηση" }, "removePasskey": { - "message": "Remove passkey" + "message": "Αφαίρεση κλειδιού πρόσβασης" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "Το κλειδί πρόσβασης αφαιρέθηκε" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Σφάλμα κατά την ανάθεση συλλογής προορισμού." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Σφάλμα κατά την ανάθεση φακέλου προορισμού." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Προβολή αντικειμένων στο $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2973,7 +2979,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Πίσω στο $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2983,11 +2989,11 @@ } }, "back": { - "message": "Back", + "message": "Πίσω", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Αφαίρεση $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index c0ce5c17ee2..7b7dc28b31d 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -792,6 +792,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -1368,7 +1380,7 @@ }, "exportPasswordDescription": { "message": "This password will be used to export and import this file" - }, + }, "accountRestrictedOptionDescription": { "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." }, @@ -1693,8 +1705,8 @@ "masterPasswordPolicyRequirementsNotMet": { "message": "Your new master password does not meet the policy requirements." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -2805,8 +2817,8 @@ "invalidFilePassword": { "message": "Invalid file password, please use the password you entered when you created the export file." }, - "importDestination": { - "message": "Import destination" + "destination": { + "message": "Destination" }, "learnAboutImportOptions": { "message": "Learn about your import options" @@ -3001,5 +3013,8 @@ "example": "Work" } } + }, + "data": { + "message": "Data" } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 9c7aea781d4..bd439742edc 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 5e404b77f09..1cc1e2235ae 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 8bde0fc3c9b..02bf89abf31 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index f31fbe6ba82..2e9a6752433 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -540,16 +540,16 @@ } }, "masterPassword": { - "message": "Master password" + "message": "Contraseña maestra" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "¡Tu contraseña maestra no se puede recuperar si la olvidas!" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "Confirmar contraseña maestra" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Pista de la contraseña maestra" }, "settings": { "message": "Ajustes" @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Código de verificación requerido." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Código de verificación incorrecto" }, @@ -679,17 +682,17 @@ "message": "Aplicación de autenticación" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "Introduce un código generado por una aplicación de autenticación como Bitwarden Authenticator.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "Llave de seguridad Yubico OTP" }, "yubiKeyDesc": { "message": "Usa un Yubikey para acceder a tu cuenta. Funciona con YubiKey 4, 4 Nano, 4C y dispositivos NEO." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "Introduce un código generado por Duo Security.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -706,7 +709,7 @@ "message": "Correo electrónico" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Introduce el código enviado a tu dirección de correo electrónico." }, "loginUnavailable": { "message": "Entrada no disponible" @@ -1968,7 +1971,7 @@ "message": "Verificación de correo electrónico requerida" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "Correo electrónico verificado" }, "emailVerificationRequiredDesc": { "message": "Debes verificar tu correo electrónico para usar esta característica." @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Inicia Duo y sigue los pasos para terminar de iniciar sesión." }, diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 4c3ba957ea4..736e14845b7 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Nõutav on kinnituskood." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Vale kinnituskood" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 1ecfb02d902..58e77b10920 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Egiaztatze-kodea behar da." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Egiaztatze-kodea ez da baliozkoa" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index 29cde5e0cad..3f20bd7a58e 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "کد تأیید مورد نیاز است." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 2da6d22607c..5f7eb42dacf 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Todennuskoodi vaaditaan." }, + "webauthnCancelOrTimeout": { + "message": "Todennus peruutettiin tai siinä kesti liian kauan. Yritä uudelleen." + }, "invalidVerificationCode": { "message": "Virheellinen todennuskoodi" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Virhe yhdistettäessä Duo-palveluun. Käytä toista kaksivaiheista kirjautumismenetelmää tai ota yhteyttä Duoon saadaksesi apua." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Avaa Duo ja viimeistele kirjautuminen seuraamalla ohjeita." }, diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 0d4d6e8c8ab..9a767f2e007 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Kailangan ang verification code." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Maling verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 8bdbf19bfaa..4bba4978d2d 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Le code de vérification est requis." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Code de vérification invalide" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Lancez Duo et suivez les étapes pour terminer la connexion." }, diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index fa4dcc02b87..9ca3726f0c4 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index ebad16e89c8..c2e3febcbd0 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "נדרש קוד אימות." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "קוד אימות שגוי" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index d894e487d30..e4f6d33e64d 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 67b671ed196..5999ffe078b 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Potvrdni kôd je obavezan." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Nevažeći kôd za provjeru" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 3ffdd7ef1e6..082adad666e 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Az ellenőrző kód kötelező." }, + "webauthnCancelOrTimeout": { + "message": "A hitelesítés megszakításra került vagy túl sokáig tartott. Próbáljuk újra." + }, "invalidVerificationCode": { "message": "Érvénytelen ellenőrző kód" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Hiba történt a Duo szolgáltatáshoz csatlakozáskor. Használjunk másik kétlépcsős bejelentkezési módot vagy kérjünk segítséget a Duotól." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Indítsuk el a DUO-t és kövessük a lépéseket a bejelentkezés befejezéséhez." }, diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 6a45046ea59..52e331ec600 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Kode verifikasi diperlukan." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Kode verifikasi tidak valid" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index 95131aa47dc..b63e8e46e74 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Codice di verifica obbligatorio." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Codice di verifica non valido" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Avvia Duo e segui i passaggi per finire di accedere." }, diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index bffd5de8396..32fb3e07bdf 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "認証コードは必須項目です。" }, + "webauthnCancelOrTimeout": { + "message": "認証がキャンセルされたか、時間がかかりすぎました。もう一度やり直してください。" + }, "invalidVerificationCode": { "message": "認証コードが間違っています" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Duo サービスへの接続中にエラーが発生しました。異なる二段階ログイン方法を使用するか、Duo に連絡してください。" + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "ログインを完了するには Duo を起動し手順に従ってください。" }, diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index fa4dcc02b87..9ca3726f0c4 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index fa4dcc02b87..9ca3726f0c4 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 9866dafb8fe..1f6843e86a3 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "ಪರಿಶೀಲನೆ ಕೋಡ್ ಅಗತ್ಯವಿದೆ." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index c2f3437f608..62d4ccec61a 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "인증 코드는 반드시 입력해야 합니다." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "유효하지 않은 확인 코드" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index 77e9f40e25b..74cf0ef9637 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Būtinas patvirtinimo kodas." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Neteisingas patvirtinimo kodas" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index fe6aad78be7..36f089d935d 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Ir nepieciešams apstiprinājuma kods." }, + "webauthnCancelOrTimeout": { + "message": "Autentifikācija tika atcelta vai tā aizņēma pārāk daudz laika. Lūgums mēģināt vēlreiz." + }, "invalidVerificationCode": { "message": "Nederīgs apstiprinājuma kods" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Kļūda savienojuma izveidošanā ar Duo pakalpojumu. Jāizmanto cits divpakāpju pieteikšanāš veids vai jāvēršas pie Duo pēc palīdzības." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Jāpalaiž Duo un jāseko soļiem, lai pabeigtu pieteikšanos." }, diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 71e9fe25948..bc6ee3c51a8 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Potreban je verifikacioni kod." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 6bd7a323d73..9ab12aabecd 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "പരിശോധിച്ചുറപ്പിക്കൽ കോഡ് ആവശ്യമാണ്." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index fa4dcc02b87..9ca3726f0c4 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 90632de6251..99b8001d97f 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index e0f5fa0d532..3265be0677b 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "En verifiseringskode er påkrevd." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Ugyldig verifiseringskode" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index a023d51c1b7..8598b501e9e 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 3ea2be8da9b..e8dc5c18c2b 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verificatiecode vereist." }, + "webauthnCancelOrTimeout": { + "message": "De authenticatie werd geannuleerd of duurde te lang. Probeer het opnieuw." + }, "invalidVerificationCode": { "message": "Ongeldige verificatiecode" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Fout bij het verbinden met de Duo-service. Gebruik een andere tweestapsaanmeldingsmethode of neem contact op met Duo voor hulp." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Start Duo en volg de stappen om in te loggen." }, diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index e8cea993671..c024eed6de0 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Ein stadfestingskode er påkravt." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Ugyldig stadfestingskode" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index a939637ea8a..88ce5d5d346 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 18f4c0bf0db..ea717d0157e 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Kod weryfikacyjny jest wymagany." }, + "webauthnCancelOrTimeout": { + "message": "Uwierzytelnianie zostało anulowane lub trwało zbyt długo. Spróbuj ponownie." + }, "invalidVerificationCode": { "message": "Kod weryfikacyjny jest nieprawidłowy" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Wystąpił błąd podczas połączenia z usługą Duo. Aby uzyskać pomoc, użyj innej metody dwustopniowego logowania lub skontaktuj się z Duo." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Uruchom Duo i wykonaj kroki, aby zakończyć logowanie." }, diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 3b9d8406840..3c8321af347 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Requer o código de verificação." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Código de verificação inválido" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Inicie o Duo e siga os passos para finalizar o login." }, diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index dc7a6226849..cc7fa764bf9 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "É necessário o código de verificação." }, + "webauthnCancelOrTimeout": { + "message": "A autenticação foi cancelada ou demorou demasiado tempo. Por favor, tente novamente." + }, "invalidVerificationCode": { "message": "Código de verificação inválido" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Erro ao ligar ao serviço Duo. Utilize um método de verificação de dois passos diferente ou contacte o Duo para obter assistência." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Inicie o Duo e siga os passos para concluir o início de sessão." }, diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 2dcc23073e4..42a3879c42b 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Este necesar codul de verificare." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Cod de verificare nevalid" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index ecfb9437124..e09961b922e 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Необходим код подтверждения." }, + "webauthnCancelOrTimeout": { + "message": "Аутентификация была отменена или заняла слишком много времени. Пожалуйста, попробуйте еще раз." + }, "invalidVerificationCode": { "message": "Неверный код подтверждения" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Ошибка при подключении к сервису Duo. Используйте другой метод двухэтапной аутентификации или обратитесь за помощью в Duo." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Запустите Duo и следуйте шагам для завершения авторизации." }, diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 0ac4b332235..3c069d841e5 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 06ef1668103..636925a39dc 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Overovací kód je povinný." }, + "webauthnCancelOrTimeout": { + "message": "Overenie bolo zrušené alebo trvalo príliš dlho. Skúste to znova." + }, "invalidVerificationCode": { "message": "Neplatný verifikačný kód" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Chyba pri pripájaní k službe Duo. Použite inú metódu dvojstupňového prihlásenia alebo kontaktujte Duo a požiadajte o pomoc." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Spustite DuO a postupujte podľa pokynov na dokončenie prihlásenia." }, diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 9fc6f48f400..0b82b67963d 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Potrebna je verifikacijska koda." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Neveljavna verifikacijska koda" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 33b4557f27b..d715a5062a9 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Верификациони код је обавезан." }, + "webauthnCancelOrTimeout": { + "message": "Аутентификација је отказана или је трајала предуго. Молим вас, покушајте поново." + }, "invalidVerificationCode": { "message": "Неисправан верификациони код" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Грешка при повезивању са услугом Duo. Користите други метод пријаве у два корака или контактирајте Duo за помоћ." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Покренути Duo и пратите кораке да бисте завршили пријављивање." }, diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index b90451b78a1..61abab33235 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verifieringskod krävs." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Ogiltig verifieringskod" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Starta Duo och följ stegen för att slutföra inloggningen." }, diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index fa4dcc02b87..9ca3726f0c4 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index a0df28b7c6e..97be1e3ecfa 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "จำเป็นต้องมีรหัสการตรวจสอบยืนยัน" }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "รหัสการตรวจสอบสิทธิ์ไม่ถูกต้อง" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index c3a100394c9..796b35c8181 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Doğrulama kodu gereklidir." }, + "webauthnCancelOrTimeout": { + "message": "Kimlik doğrulama iptal edildi ve çok uzun sürdü. Lütfen yeniden deneyin." + }, "invalidVerificationCode": { "message": "Geçersiz doğrulama kodu" }, @@ -683,7 +686,7 @@ "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "YubiKey OTP güvenlik anahtarı" }, "yubiKeyDesc": { "message": "Hesabınıza erişmek için bir YubiKey kullanın. YubiKey 4, 4 Nano, 4C ve NEO cihazlarıyla çalışır." @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Duo'yu başlatın ve oturum açmayı tamamlamak için adımları izleyin." }, @@ -2812,7 +2818,7 @@ "message": "Bir koleksiyon seçin" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "İçe aktarılan dosya içeriklerinin $DESTINATION$ konumuna taşınmasını istiyorsanız bu seçeneği seçin", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -2963,7 +2969,7 @@ "message": "Hedef klasör atama hatası." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "$NAME$ içindeki kayıtları görüntüle", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2973,7 +2979,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "$NAME$ klasörüne dön", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index 3b3d5263153..30c2034b2ae 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Потрібний код підтвердження." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Недійсний код підтвердження" }, @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Запустіть Duo і виконайте дії для завершення входу." }, diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 26f7feec398..7f3ad1b0299 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -404,7 +404,7 @@ "message": "Độ dài" }, "passwordMinLength": { - "message": "Minimum password length" + "message": "Độ dài mật khẩu tối thiểu" }, "uppercase": { "message": "Chữ in hoa (A-Z)" @@ -479,7 +479,7 @@ "message": "Kích thước tối đa của tệp tin là 500MB." }, "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "message": "Cần di chuyển khóa mã hóa. Vui lòng đăng nhập trang web Bitwaden để cập nhật khóa mã hóa của bạn." }, "editedFolder": { "message": "Đã chỉnh sửa thư mục" @@ -500,7 +500,7 @@ "message": "Tạo Tài Khoản" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Đặt mật khẩu mạnh" }, "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" @@ -527,7 +527,7 @@ "message": "Gợi ý mật khẩu chính (tùy chọn)" }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "Nếu bạn quên mật khẩu, gợi ý mật khẩu có thể được gửi tới email của bạn. $CURRENT$/$MAXIMUM$ ký tự tối đa.", "placeholders": { "current": { "content": "$1", @@ -540,16 +540,16 @@ } }, "masterPassword": { - "message": "Master password" + "message": "Mật khẩu chính" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "Mật khẩu chính của bạn không thể phục hồi nếu bạn quên nó!" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "Nhập lại mật khẩu chính" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Gợi ý mật khẩu chính" }, "settings": { "message": "Cài đặt" @@ -586,10 +586,10 @@ } }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Bạn đã đăng nhập thành công" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Bạn có thể đóng cửa sổ này" }, "masterPassDoesntMatch": { "message": "Xác nhận mật khẩu chính không khớp." @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "Yêu cầu mã xác nhận." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Mã xác minh không hợp lệ" }, @@ -683,7 +686,7 @@ "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "Khóa bảo mật YubiKey OTP" }, "yubiKeyDesc": { "message": "Sử dụng YubiKey để truy cập tài khoản của bạn. Làm việc với thiết bị YubiKey 4, 4 Nano, 4C và NEO." @@ -706,7 +709,7 @@ "message": "Email" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Nhập mã được gửi về email của bạn." }, "loginUnavailable": { "message": "Đăng nhập không sẵn có" @@ -784,7 +787,7 @@ "message": "Đăng xuất" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Bạn đã đăng xuất khỏi tài khoản của mình." }, "loginExpired": { "message": "Phiên đăng nhập của bạn đã hết hạn." @@ -844,10 +847,10 @@ "message": "Thay đổi mật khẩu chính" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Tiếp tục tới ứng dụng web?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Bạn có thể thay đổi mật khẩu chính của mình trên Bitwarden bản web." }, "fingerprintPhrase": { "message": "Fingerprint Phrase", @@ -981,7 +984,7 @@ "message": "Thu nhỏ về thanh menu" }, "enableMinToMenuBarDesc": { - "message": "When minimizing the window, show an icon in the menu bar instead." + "message": "Khi thu nhỏ sổ, thay vào đó sẽ hiện một biểu tượng trên thanh menu." }, "enableCloseToTray": { "message": "Close to Tray Icon" @@ -990,10 +993,10 @@ "message": "Khi đóng cửa sổ, thay vào đó sẽ hiện một biểu tượng trên khay hệ thống." }, "enableCloseToMenuBar": { - "message": "Close to menu bar" + "message": "Đóng thanh menu" }, "enableCloseToMenuBarDesc": { - "message": "When closing the window, show an icon in the menu bar instead." + "message": "Khi đóng cửa sổ, thay vào đó sẽ hiện một biểu tượng trên thanh menu." }, "enableTray": { "message": "Enable Tray Icon" @@ -1349,7 +1352,7 @@ "description": "ex. Date this password was updated" }, "exportFrom": { - "message": "Export from" + "message": "Xuất từ" }, "exportVault": { "message": "Export Vault" @@ -1361,7 +1364,7 @@ "message": "This file export will be password protected and require the file password to decrypt." }, "filePassword": { - "message": "File password" + "message": "Mật khẩu tập tin" }, "exportPasswordDescription": { "message": "This password will be used to export and import this file" @@ -1370,7 +1373,7 @@ "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." }, "passwordProtected": { - "message": "Password protected" + "message": "Mật khẩu đã được bảo vệ" }, "passwordProtectedOptionDescription": { "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." @@ -1379,7 +1382,7 @@ "message": "Export type" }, "accountRestricted": { - "message": "Account restricted" + "message": "Tài khoản bị hạn chế" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“File password” and “Confirm file password“ do not match." @@ -1481,7 +1484,7 @@ "message": "Mã PIN không hợp lệ." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "Mã PIN bị gõ sai quá nhiều lần. Đang đăng xuất." }, "unlockWithWindowsHello": { "message": "Mở khóa với Windows Hello" @@ -1636,11 +1639,11 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "verificationRequired": { - "message": "Verification required", + "message": "Yêu cầu xác minh", "description": "Default title for the user verification dialog." }, "currentMasterPass": { - "message": "Current master password" + "message": "Mật khẩu chính hiện tại" }, "newMasterPass": { "message": "Mật khẩu chính mới" @@ -1652,7 +1655,7 @@ "message": "One or more organization policies require your master password to meet the following requirements:" }, "policyInEffectMinComplexity": { - "message": "Minimum complexity score of $SCORE$", + "message": "Độ mạnh tối thiểu $SCORE$", "placeholders": { "score": { "content": "$1", @@ -1661,7 +1664,7 @@ } }, "policyInEffectMinLength": { - "message": "Minimum length of $LENGTH$", + "message": "Độ dài tối thiểu là $LENGTH$", "placeholders": { "length": { "content": "$1", @@ -1694,7 +1697,7 @@ "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "Huỷ đăng ký" }, "atAnyTime": { "message": "at any time." @@ -1703,10 +1706,10 @@ "message": "By continuing, you agree to the" }, "and": { - "message": "and" + "message": "và" }, "acceptPolicies": { - "message": "By checking this box you agree to the following:" + "message": "Bạn đồng ý với những điều sau khi nhấn chọn ô này:" }, "acceptPoliciesRequired": { "message": "Điều khoản sử dụng và chính sách quyền riêng tư chưa được đồng ý." @@ -1748,10 +1751,10 @@ "message": "Add an additional layer of security by requiring fingerprint phrase confirmation when establishing a link between your desktop and browser. This requires user action and verification each time a connection is created." }, "enableHardwareAcceleration": { - "message": "Use hardware acceleration" + "message": "Dùng tính năng tăng tốc phần cứng" }, "enableHardwareAccelerationDesc": { - "message": "By default this setting is ON. Turn OFF only if you experience graphical issues. Restart is required." + "message": "Mặc định cài đặt này được bật. Chỉ tắt khi gặp sự cố đồ họa. Cần khởi động lại." }, "approve": { "message": "Chấp nhận" @@ -1796,7 +1799,7 @@ "message": "An organization policy has blocked importing items into your individual vault." }, "allSends": { - "message": "Toàn bộ Send", + "message": "Tất cả mục Gửi", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeFile": { @@ -1806,15 +1809,15 @@ "message": "Văn bản" }, "searchSends": { - "message": "Tìm kiếm Send", + "message": "Tìm kiếm mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { - "message": "Chỉnh sửa Send", + "message": "Sửa mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "myVault": { - "message": "Hầm của tôi" + "message": "Kho của tôi" }, "text": { "message": "Văn bản" @@ -1823,14 +1826,14 @@ "message": "Ngày xóa" }, "deletionDateDesc": { - "message": "Send sẽ được xóa vĩnh viễn vào ngày và giờ được chỉ định.", + "message": "Mục Gửi sẽ được xóa vĩnh viễn vào ngày và giờ chỉ định.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { "message": "Ngày Hết Hạn" }, "expirationDateDesc": { - "message": "Nếu được thiết lập, truy cập vào Send này sẽ hết hạn vào ngày và giờ được chỉ định.", + "message": "Nếu được thiết lập, mục Gửi này sẽ hết hạn vào ngày và giờ được chỉ định.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCount": { @@ -1838,57 +1841,57 @@ "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "maxAccessCountDesc": { - "message": "Nếu được thiết lập, khi đã đạt tới số lượng truy cập tối đa, người dùng sẽ không thể truy cập Send này nữa.", + "message": "Nếu được thiết lập, khi đã đạt tới số lượng truy cập tối đa, người dùng sẽ không thể truy cập mục Gửi này nữa.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "currentAccessCount": { "message": "Số lượng truy cập hiện tại" }, "disableSend": { - "message": "Deactivate this Send so that no one can access it.", + "message": "Vô hiệu hoá mục Gửi này để không ai có thể truy cập nó.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", + "message": "Yêu cầu nhập mật khẩu khi người dùng truy cập vào phần Gửi này.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendNotesDesc": { - "message": "Private notes about this Send.", + "message": "Ghi chú riêng tư về mục Gửi này.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLink": { - "message": "Gửi liên kết", + "message": "Liên kết Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinkLabel": { - "message": "Gửi liên kết", + "message": "Liên kết Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "textHiddenByDefault": { - "message": "When accessing the Send, hide the text by default", + "message": "Khi truy cập vào phần Gửi, văn bản sẽ được ẩn theo mặc định", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Đã tạo Send", + "message": "Đã tạo mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Đã chỉnh sửa Send", + "message": "Đã lưu mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { - "message": "Đã xóa Send", + "message": "Đã xóa mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { "message": "Mật khẩu mới" }, "whatTypeOfSend": { - "message": "Đây là loại Send gì?", + "message": "Đây là kiểu Gửi gì?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "Tạo Send", + "message": "Mục Gửi mới", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { @@ -1913,7 +1916,7 @@ "message": "Tùy chỉnh" }, "deleteSendConfirmation": { - "message": "Bạn có chắc chắn muốn xóa Send này?", + "message": "Bạn có chắc muốn mục Gửi này?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLinkToClipboard": { @@ -1921,10 +1924,10 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLinkOnSave": { - "message": "Copy the link to share this Send to my clipboard upon save." + "message": "Sao chép liên kết chia sẻ của mục Gửi này tới bộ nhớ tạm khi lưu." }, "sendDisabled": { - "message": "Đã tắt Send", + "message": "Đã loại bỏ Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -1938,16 +1941,16 @@ "message": "Đã tắt" }, "removePassword": { - "message": "Remove password" + "message": "Xóa mật khẩu" }, "removedPassword": { - "message": "Password removed" + "message": "Đã xóa mật khẩu" }, "removePasswordConfirmation": { - "message": "Are you sure you want to remove the password?" + "message": "Bạn có chắc muốn xóa mật khẩu này?" }, "maxAccessCountReached": { - "message": "Đã đạt đến số lượng truy cập tối đa" + "message": "Đã vượt số lần truy cập tối đa" }, "expired": { "message": "Đã hết hạn" @@ -1962,7 +1965,7 @@ "message": "Ẩn địa chỉ email của tôi khỏi người nhận." }, "sendOptionsPolicyInEffect": { - "message": "Có một hoặc vài chính sách của tổ chức đang làm ảnh hưởng đến cài đặt tạo mật khẩu của bạn." + "message": "Các chính sách của tổ chức đang ảnh hưởng đến tùy chọn Gửi của bạn." }, "emailVerificationRequired": { "message": "Yêu cầu xác nhận danh tính qua Email" @@ -1995,19 +1998,19 @@ "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, "tryAgain": { - "message": "Try again" + "message": "Thử lại" }, "verificationRequiredForActionSetPinToContinue": { "message": "Verification required for this action. Set a PIN to continue." }, "setPin": { - "message": "Set PIN" + "message": "Thiết lập mã PIN" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "Xác thực bằng sinh trắc học" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "Đang chờ xác nhận" }, "couldNotCompleteBiometrics": { "message": "Could not complete biometrics." @@ -2016,19 +2019,19 @@ "message": "Need a different method?" }, "useMasterPassword": { - "message": "Use master password" + "message": "Dùng mật khẩu chính" }, "usePin": { - "message": "Use PIN" + "message": "Dùng mã PIN" }, "useBiometrics": { - "message": "Use biometrics" + "message": "Dùng sinh trắc học" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "Nhập mã xác minh được gửi đến email của bạn." }, "resendCode": { - "message": "Resend code" + "message": "Gửi lại mã" }, "hours": { "message": "Giờ" @@ -2050,7 +2053,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s). Your vault timeout action is set to $ACTION$.", + "message": "Tổ chức của bạn đang ảnh hưởng đến thời gian mở kho. Thời gian mở kho tối đa là $HOURS$ giờ và $MINUTES$ phút. Kho sẽ $ACTION$ sau khi hết thời gian mở kho.", "placeholders": { "hours": { "content": "$1", @@ -2067,7 +2070,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "Your organization policies have set your vault timeout action to $ACTION$.", + "message": "Tổ chức của bạn sẽ $ACTION$ sau khi hết thời gian mở kho.", "placeholders": { "action": { "content": "$1", @@ -2082,7 +2085,7 @@ "message": "Đăng ký tự động" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password." + "message": "Tổ chức này có chính sách doanh nghiệp sẽ tự động đặt lại mật khẩu chính cho bạn. Đăng ký sẽ cho phép quản trị viên tổ chức thay đổi mật khẩu chính của bạn." }, "vaultExportDisabled": { "message": "Vault export removed" @@ -2115,13 +2118,13 @@ "message": "Are you sure you want to leave this organization?" }, "leftOrganization": { - "message": "You have left the organization." + "message": "Bạn đã rời khỏi tổ chức." }, "ssoKeyConnectorError": { "message": "Key connector error: make sure key connector is available and working correctly." }, "lockAllVaults": { - "message": "Lock all vaults" + "message": "Khoá tất cả kho lưu trữ" }, "accountLimitReached": { "message": "No more than 5 accounts may be logged in at the same time." @@ -2130,7 +2133,7 @@ "message": "Tuỳ chỉnh" }, "appPreferences": { - "message": "App settings (all accounts)" + "message": "Cài đặt ứng dụng (tất cả tài khoản)" }, "accountSwitcherLimitReached": { "message": "Account limit reached. Log out of an account to add another." @@ -2148,7 +2151,7 @@ "message": "Chuyển tài khoản" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Bạn đã có tài khoản?" }, "options": { "message": "Tùy chọn" @@ -2196,16 +2199,16 @@ "message": "Loại mật khẩu" }, "regenerateUsername": { - "message": "Regenerate username" + "message": "Tạo lại tên người dùng" }, "generateUsername": { "message": "Tạo tên tài khoản" }, "usernameType": { - "message": "Username type" + "message": "Loại tên người dùng" }, "plusAddressedEmail": { - "message": "Plus addressed email", + "message": "Địa chỉ email có hậu tố", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { @@ -2236,13 +2239,13 @@ "message": "Tìm tổ chức" }, "searchMyVault": { - "message": "Search my vault" + "message": "Tìm kiếm trong kho" }, "forwardedEmail": { - "message": "Forwarded email alias" + "message": "Đã chuyển tiếp bí danh email" }, "forwardedEmailDesc": { - "message": "Generate an email alias with an external forwarding service." + "message": "Tạo bí danh email với dịch vụ chuyển tiếp bên ngoài." }, "forwarderError": { "message": "$SERVICENAME$ error: $ERRORMESSAGE$", @@ -2347,7 +2350,7 @@ } }, "hostname": { - "message": "Hostname", + "message": "Tên máy chủ", "description": "Part of a URL." }, "apiAccessToken": { @@ -2357,7 +2360,7 @@ "message": "Khóa API" }, "premiumSubcriptionRequired": { - "message": "Premium subscription required" + "message": "Yêu cầu đăng ký gói Premium" }, "organizationIsDisabled": { "message": "Organization suspended" @@ -2369,25 +2372,25 @@ "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." }, "vault": { - "message": "Vault" + "message": "Kho mật khẩu" }, "loginWithMasterPassword": { - "message": "Log in with master password" + "message": "Đăng nhập bằng mật khẩu chính" }, "loggingInAs": { "message": "Logging in as" }, "rememberEmail": { - "message": "Remember email" + "message": "Ghi nhớ email" }, "notYou": { - "message": "Not you?" + "message": "Không phải bạn?" }, "newAroundHere": { - "message": "New around here?" + "message": "Bạn mới tới đây sao?" }, "loggingInTo": { - "message": "Logging in to $DOMAIN$", + "message": "Đang đăng nhập vào $DOMAIN$", "placeholders": { "domain": { "content": "$1", @@ -2396,13 +2399,13 @@ } }, "logInWithAnotherDevice": { - "message": "Log in with another device" + "message": "Đăng nhập bằng thiết bị khác" }, "loginInitiated": { "message": "Login initiated" }, "notificationSentDevice": { - "message": "A notification has been sent to your device." + "message": "Một thông báo đã được gửi đến thiết bị của bạn." }, "fingerprintMatchInfo": { "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." @@ -2411,20 +2414,20 @@ "message": "Fingerprint phrase" }, "needAnotherOption": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" + "message": "Đăng nhập bằng thiết bị phải được thiết lập trong cài đặt của ứng dụng Bitwarden. Dùng cách khác?" }, "viewAllLoginOptions": { - "message": "View all login options" + "message": "Xem tất cả tùy chọn đăng nhập" }, "resendNotification": { - "message": "Resend notification" + "message": "Gửi lại thông báo" }, "toggleCharacterCount": { - "message": "Toggle character count", + "message": "Bật tắt đếm kí tự", "description": "'Character count' describes a feature that displays a number next to each character of the password." }, "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "message": "Bạn đang cố đăng nhập?" }, "logInAttemptBy": { "message": "Login attempt by $EMAIL$", @@ -2436,22 +2439,22 @@ } }, "deviceType": { - "message": "Device Type" + "message": "Loại thiết bị" }, "ipAddress": { - "message": "IP Address" + "message": "Địa chỉ IP" }, "time": { - "message": "Time" + "message": "Thời Gian" }, "confirmLogIn": { - "message": "Confirm login" + "message": "Xác nhận đăng nhập" }, "denyLogIn": { - "message": "Deny login" + "message": "Từ chối đăng nhập" }, "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "message": "Đã xác nhận đăng nhập cho $EMAIL$ trên $DEVICE$", "placeholders": { "email": { "content": "$1", @@ -2464,13 +2467,13 @@ } }, "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + "message": "Bạn đã từ chối đăng nhập từ thiết bị khác. Nếu đó là bạn, hãy thử đăng nhập lại bằng thiết bị đó." }, "justNow": { - "message": "Just now" + "message": "Vừa xong" }, "requestedXMinutesAgo": { - "message": "Requested $MINUTES$ minutes ago", + "message": "Đã yêu cầu $MINUTES$ phút trước", "placeholders": { "minutes": { "content": "$1", @@ -2479,13 +2482,13 @@ } }, "loginRequestHasAlreadyExpired": { - "message": "Login request has already expired." + "message": "Yêu cầu đăng nhập đã hết hạn." }, "thisRequestIsNoLongerValid": { - "message": "This request is no longer valid." + "message": "Yêu cầu này không còn hiệu lực." }, "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "message": "Phê duyệt đăng nhập cho $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2494,46 +2497,46 @@ } }, "logInRequested": { - "message": "Log in requested" + "message": "Yêu cầu đăng nhập" }, "creatingAccountOn": { "message": "Creating account on" }, "checkYourEmail": { - "message": "Check your email" + "message": "Kiểm tra email của bạn" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "Nhấp vào liên kết trong email được gửi đến" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "và tiếp tục tạo tài khoản của bạn." }, "noEmail": { - "message": "No email?" + "message": "Không có email?" }, "goBack": { - "message": "Go back" + "message": "Quay lại" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "để chỉnh sửa địa chỉ email của bạn." }, "exposedMasterPassword": { - "message": "Exposed Master Password" + "message": "Mật khẩu chính bị lộ" }, "exposedMasterPasswordDesc": { - "message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?" + "message": "Mật khẩu này đã bị rò rỉ trong một vụ tấn công dữ liệu. Dùng mật khẩu mới và an toàn để bảo vệ tài khoản bạn. Bạn có chắc muốn sử dụng mật khẩu đã bị rò rỉ?" }, "weakAndExposedMasterPassword": { - "message": "Weak and Exposed Master Password" + "message": "Mật khẩu chính Yếu và Bị Lộ" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" + "message": "Mật khẩu yếu này đã bị rò rỉ trong một vụ tấn công dữ liệu. Dùng mật khẩu mới và an toàn để bảo vệ tài khoản bạn. Bạn có chắc muốn sử dụng mật khẩu đã bị rò rỉ?" }, "checkForBreaches": { - "message": "Check known data breaches for this password" + "message": "Kiểm tra mật khẩu có lộ trong các vụ rò rỉ dữ liệu hay không" }, "important": { - "message": "Important:" + "message": "Quan trọng:" }, "accessTokenUnableToBeDecrypted": { "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." @@ -2542,7 +2545,7 @@ "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." }, "masterPasswordHint": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "Mật khẩu chính của bạn không thể phục hồi nếu bạn quên nó!" }, "characterMinimum": { "message": "$LENGTH$ character minimum", @@ -2563,71 +2566,71 @@ "message": "Device approval required. Select an approval option below:" }, "rememberThisDevice": { - "message": "Remember this device" + "message": "Lưu thiết bị này" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "Bỏ chọn nếu sử dụng thiết bị công cộng" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "Phê duyệt bằng thiết bị khác" }, "requestAdminApproval": { - "message": "Request admin approval" + "message": "Yêu cầu quản trị viên phê duyệt" }, "approveWithMasterPassword": { - "message": "Approve with master password" + "message": "Phê duyệt bằng mật khẩu chính" }, "region": { - "message": "Region" + "message": "Khu vực" }, "ssoIdentifierRequired": { - "message": "Organization SSO identifier is required." + "message": "Cần có mã định danh SSO của tổ chức." }, "eu": { - "message": "EU", + "message": "Châu Âu", "description": "European Union" }, "loggingInOn": { "message": "Logging in on" }, "selfHostedServer": { - "message": "self-hosted" + "message": "tự lưu trữ" }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, "accountSuccessfullyCreated": { - "message": "Account successfully created!" + "message": "Tạo tài khoản thành công!" }, "adminApprovalRequested": { "message": "Admin approval requested" }, "adminApprovalRequestSentToAdmins": { - "message": "Your request has been sent to your admin." + "message": "Yêu cầu của bạn đã được gửi đến quản trị viên." }, "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." + "message": "Bạn sẽ có thông báo nếu được phê duyệt." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "Không thể đăng nhập?" }, "loginApproved": { - "message": "Login approved" + "message": "Lượt đăng nhập đã duyệt" }, "userEmailMissing": { - "message": "User email missing" + "message": "Thiếu email người dùng" }, "deviceTrusted": { - "message": "Device trusted" + "message": "Thiết bị tin cậy" }, "inputRequired": { - "message": "Input is required." + "message": "Không được để trống." }, "required": { - "message": "required" + "message": "bắt buộc" }, "search": { - "message": "Search" + "message": "Tìm kiếm" }, "inputMinLength": { "message": "Input must be at least $COUNT$ characters long.", @@ -2694,19 +2697,19 @@ } }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- Chọn --" }, "multiSelectPlaceholder": { - "message": "-- Type to filter --" + "message": "-- Nhập để lọc --" }, "multiSelectLoading": { "message": "Retrieving options..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "Không tìm thấy mục nào" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "Xoá tất cả" }, "plusNMore": { "message": "+ $QUANTITY$ more", @@ -2730,7 +2733,7 @@ "message": "Passkey" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "Không thể sao chép passkey" }, "passkeyNotCopiedAlert": { "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" @@ -2739,11 +2742,11 @@ "message": "Alias domain" }, "importData": { - "message": "Import data", + "message": "Nhập dữ liệu", "description": "Used for the desktop menu item and the header of the import dialog" }, "importError": { - "message": "Import error" + "message": "Lỗi nhập" }, "importErrorDesc": { "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." @@ -2752,13 +2755,13 @@ "message": "Resolve the errors below and try again." }, "description": { - "message": "Description" + "message": "Mô tả" }, "importSuccess": { - "message": "Data successfully imported" + "message": "Dữ liệu đã được nhập thành công" }, "importSuccessNumberOfItems": { - "message": "A total of $AMOUNT$ items were imported.", + "message": "Đã nhập tổng cộng $AMOUNT$ mục.", "placeholders": { "amount": { "content": "$1", @@ -2767,7 +2770,7 @@ } }, "total": { - "message": "Total" + "message": "Tổng" }, "importWarning": { "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -2788,10 +2794,10 @@ "message": "Launch Duo in Browser" }, "importFormatError": { - "message": "Data is not formatted correctly. Please check your import file and try again." + "message": "Dữ liệu không được định dạng đúng cách, vui lòng kiểm tra và thử lại." }, "importNothingError": { - "message": "Nothing was imported." + "message": "Chưa nhập gì." }, "importEncKeyError": { "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." @@ -2803,13 +2809,13 @@ "message": "Import destination" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "Tìm hiểu các tuỳ chọn nhập của bạn" }, "selectImportFolder": { - "message": "Select a folder" + "message": "Chọn thư mục" }, "selectImportCollection": { - "message": "Select a collection" + "message": "Chọn bộ sưu tập" }, "importTargetHint": { "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", @@ -2828,19 +2834,19 @@ "message": "Select the format of the import file" }, "selectImportFile": { - "message": "Select the import file" + "message": "Chọn tập tin nhập" }, "chooseFile": { - "message": "Choose File" + "message": "Chọn tập tin" }, "noFileChosen": { - "message": "No file chosen" + "message": "Chưa chọn tập tin nào" }, "orCopyPasteFileContents": { - "message": "or copy/paste the import file contents" + "message": "hoặc sao chép/dán để nhập nội dung file" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "Hướng dẫn dùng $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -2850,16 +2856,16 @@ } }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "Xác nhận nhập kho lưu trữ" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "Tập tin này được bảo vệ bằng mật khẩu. Vui lòng nhập mật khẩu để nhập dữ liệu." }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "Nhập lại mật khẩu tập tin" }, "exportSuccess": { - "message": "Vault data exported" + "message": "Đã xuất dữ liệu kho của bạn" }, "multifactorAuthenticationCancelled": { "message": "Multifactor authentication cancelled" @@ -2868,16 +2874,16 @@ "message": "No LastPass data found" }, "incorrectUsernameOrPassword": { - "message": "Incorrect username or password" + "message": "Tên người dùng hoặc mật khẩu không đúng" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "Mật khẩu không đúng" }, "incorrectCode": { - "message": "Incorrect code" + "message": "Mã không đúng" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "Mã PIN không đúng" }, "multifactorAuthenticationFailed": { "message": "Multifactor authentication failed" @@ -2923,38 +2929,38 @@ "message": "Import directly from LastPass" }, "importFromCSV": { - "message": "Import from CSV" + "message": "Nhập từ CSV" }, "lastPassTryAgainCheckEmail": { "message": "Try again or look for an email from LastPass to verify it's you." }, "collection": { - "message": "Collection" + "message": "Bộ Sưu Tập" }, "lastPassYubikeyDesc": { "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." }, "commonImportFormats": { - "message": "Common formats", + "message": "Định dạng chung", "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "Thành công" }, "troubleshooting": { - "message": "Troubleshooting" + "message": "Khắc phục sự cố" }, "disableHardwareAccelerationRestart": { - "message": "Disable hardware acceleration and restart" + "message": "Vô hiệu hoá tính năng tăng tốc phần cứng và khởi động lại" }, "enableHardwareAccelerationRestart": { - "message": "Enable hardware acceleration and restart" + "message": "Kích hoạt tính năng tăng tốc phần cứng và khởi động lại" }, "removePasskey": { - "message": "Remove passkey" + "message": "Xóa passkey" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "Đã xóa passkey" }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." @@ -2963,7 +2969,7 @@ "message": "Error assigning target folder." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Xem các mục trong $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2973,7 +2979,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Quay lại $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2983,11 +2989,11 @@ } }, "back": { - "message": "Back", + "message": "Quay lại", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Xoá $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index a874b2ff5ef..cbceb244caf 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "必须填写验证码。" }, + "webauthnCancelOrTimeout": { + "message": "身份验证被取消或耗时过长。请重试。" + }, "invalidVerificationCode": { "message": "无效的验证码" }, @@ -811,7 +814,7 @@ "message": "账户" }, "loading": { - "message": "正在加载..." + "message": "加载中…" }, "lockVault": { "message": "锁定密码库" @@ -1995,7 +1998,7 @@ "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, "tryAgain": { - "message": "再试一次" + "message": "请重试" }, "verificationRequiredForActionSetPinToContinue": { "message": "此操作需要验证。设置一个 PIN 码以继续。" @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "与 Duo 服务连接时出错。使用不同的两步登录方式或联系 Duo 寻求帮助。" + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "启动 Duo 然后按照步骤完成登录。" }, diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 427997aa543..9d18a6ba15c 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -627,6 +627,9 @@ "verificationCodeRequired": { "message": "必須填入驗證碼。" }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "無效的驗證碼" }, @@ -1361,7 +1364,7 @@ "message": "This file export will be password protected and require the file password to decrypt." }, "filePassword": { - "message": "File password" + "message": "檔案密碼" }, "exportPasswordDescription": { "message": "This password will be used to export and import this file" @@ -2778,6 +2781,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts index e82d16ee9fd..e690eef71c8 100644 --- a/apps/desktop/src/main/window.main.ts +++ b/apps/desktop/src/main/window.main.ts @@ -53,11 +53,9 @@ export class WindowMain { this.win.setBackgroundColor(await this.getBackgroundColor()); // By default some linux distro collect core dumps on crashes which gets written to disk. - if (!isLinux()) { - const crashEvent = once(this.win.webContents, "render-process-gone"); - this.win.webContents.forcefullyCrashRenderer(); - await crashEvent; - } + const crashEvent = once(this.win.webContents, "render-process-gone"); + this.win.webContents.forcefullyCrashRenderer(); + await crashEvent; this.win.webContents.reloadIgnoringCache(); // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. diff --git a/apps/desktop/src/platform/services/electron-crypto.service.spec.ts b/apps/desktop/src/platform/services/electron-crypto.service.spec.ts index 0deeca2d41d..debbd0aa9b4 100644 --- a/apps/desktop/src/platform/services/electron-crypto.service.spec.ts +++ b/apps/desktop/src/platform/services/electron-crypto.service.spec.ts @@ -109,14 +109,6 @@ describe("electronCryptoService", () => { userId: mockUserId, }); }); - - it("clears the old deprecated Biometric key whenever a User Key is set", async () => { - await sut.setUserKey(mockUserKey, mockUserId); - - expect(stateService.setCryptoMasterKeyBiometric).toHaveBeenCalledWith(null, { - userId: mockUserId, - }); - }); }); }); }); diff --git a/apps/desktop/src/platform/services/electron-crypto.service.ts b/apps/desktop/src/platform/services/electron-crypto.service.ts index 1bbd02ab8b9..10982e7270f 100644 --- a/apps/desktop/src/platform/services/electron-crypto.service.ts +++ b/apps/desktop/src/platform/services/electron-crypto.service.ts @@ -13,13 +13,12 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { CryptoService } from "@bitwarden/common/platform/services/crypto.service"; import { StateProvider } from "@bitwarden/common/platform/state"; import { CsprngString } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; -import { UserKey, MasterKey } from "@bitwarden/common/types/key"; +import { UserKey } from "@bitwarden/common/types/key"; export class ElectronCryptoService extends CryptoService { constructor( @@ -53,9 +52,7 @@ export class ElectronCryptoService extends CryptoService { override async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: UserId): Promise { if (keySuffix === KeySuffixOptions.Biometric) { - // TODO: Remove after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3474) - const oldKey = await this.stateService.hasCryptoMasterKeyBiometric({ userId: userId }); - return oldKey || (await this.stateService.hasUserKeyBiometric({ userId: userId })); + return await this.stateService.hasUserKeyBiometric({ userId: userId }); } return super.hasUserKeyStored(keySuffix, userId); } @@ -90,7 +87,6 @@ export class ElectronCryptoService extends CryptoService { userId?: UserId, ): Promise { if (keySuffix === KeySuffixOptions.Biometric) { - await this.migrateBiometricKeyIfNeeded(userId); const userKey = await this.stateService.getUserKeyBiometric({ userId: userId }); return userKey == null ? null @@ -149,46 +145,4 @@ export class ElectronCryptoService extends CryptoService { return biometricKey; } - - // --LEGACY METHODS-- - // We previously used the master key for additional keys, but now we use the user key. - // These methods support migrating the old keys to the new ones. - // TODO: Remove after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3475) - - override async clearDeprecatedKeys(keySuffix: KeySuffixOptions, userId?: UserId) { - if (keySuffix === KeySuffixOptions.Biometric) { - await this.stateService.setCryptoMasterKeyBiometric(null, { userId: userId }); - } - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - super.clearDeprecatedKeys(keySuffix, userId); - } - - private async migrateBiometricKeyIfNeeded(userId?: UserId) { - if (await this.stateService.hasCryptoMasterKeyBiometric({ userId })) { - const oldBiometricKey = await this.stateService.getCryptoMasterKeyBiometric({ userId }); - // decrypt - const masterKey = new SymmetricCryptoKey(Utils.fromB64ToArray(oldBiometricKey)) as MasterKey; - userId ??= (await firstValueFrom(this.accountService.activeAccount$))?.id; - const encUserKeyPrim = await this.stateService.getEncryptedCryptoSymmetricKey({ - userId: userId, - }); - const encUserKey = - encUserKeyPrim != null - ? new EncString(encUserKeyPrim) - : await this.masterPasswordService.getMasterKeyEncryptedUserKey(userId); - if (!encUserKey) { - throw new Error("No user key found during biometric migration"); - } - const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey( - masterKey, - encUserKey, - userId, - ); - // migrate - await this.storeBiometricKey(userKey, userId); - await this.stateService.setCryptoMasterKeyBiometric(null, { userId }); - } - } } diff --git a/apps/desktop/src/services/native-messaging.service.ts b/apps/desktop/src/services/native-messaging.service.ts index 7f6d39b2e8d..2a5c341ee7b 100644 --- a/apps/desktop/src/services/native-messaging.service.ts +++ b/apps/desktop/src/services/native-messaging.service.ts @@ -3,7 +3,6 @@ import { firstValueFrom, map } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { MasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -34,7 +33,6 @@ export class NativeMessagingService { private sharedSecrets = new Map(); constructor( - private masterPasswordService: MasterPasswordServiceAbstraction, private cryptoFunctionService: CryptoFunctionService, private cryptoService: CryptoService, private platformUtilService: PlatformUtilsService, @@ -177,19 +175,12 @@ export class NativeMessagingService { KeySuffixOptions.Biometric, message.userId, ); - const masterKey = await firstValueFrom( - this.masterPasswordService.masterKey$(message.userId as UserId), - ); if (userKey != null) { - // we send the master key still for backwards compatibility - // with older browser extensions - // TODO: Remove after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3472) await this.send( { command: "biometricUnlock", response: "unlocked", - keyB64: masterKey?.keyB64, userKeyB64: userKey.keyB64, }, appId, diff --git a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts new file mode 100644 index 00000000000..75e63d42428 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts @@ -0,0 +1,126 @@ +import { Component } from "@angular/core"; +import { TestBed } from "@angular/core/testing"; +import { provideRouter } from "@angular/router"; +import { RouterTestingHarness } from "@angular/router/testing"; +import { MockProxy, any, mock } from "jest-mock-extended"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { ProductTierType } from "@bitwarden/common/billing/enums"; +import { DialogService } from "@bitwarden/components"; + +import { isEnterpriseOrgGuard } from "./is-enterprise-org.guard"; + +@Component({ + template: "

This is the home screen!

", +}) +export class HomescreenComponent {} + +@Component({ + template: "

This component can only be accessed by a enterprise organization!

", +}) +export class IsEnterpriseOrganizationComponent {} + +@Component({ + template: "

This is the organization upgrade screen!

", +}) +export class OrganizationUpgradeScreenComponent {} + +const orgFactory = (props: Partial = {}) => + Object.assign( + new Organization(), + { + id: "myOrgId", + enabled: true, + type: OrganizationUserType.Admin, + }, + props, + ); + +describe("Is Enterprise Org Guard", () => { + let organizationService: MockProxy; + let dialogService: MockProxy; + let routerHarness: RouterTestingHarness; + + beforeEach(async () => { + organizationService = mock(); + dialogService = mock(); + + TestBed.configureTestingModule({ + providers: [ + { provide: OrganizationService, useValue: organizationService }, + { provide: DialogService, useValue: dialogService }, + provideRouter([ + { + path: "", + component: HomescreenComponent, + }, + { + path: "organizations/:organizationId/enterpriseOrgsOnly", + component: IsEnterpriseOrganizationComponent, + canActivate: [isEnterpriseOrgGuard()], + }, + { + path: "organizations/:organizationId/billing/subscription", + component: OrganizationUpgradeScreenComponent, + }, + ]), + ], + }); + + routerHarness = await RouterTestingHarness.create(); + }); + + it("redirects to `/` if the organization id provided is not found", async () => { + const org = orgFactory(); + organizationService.get.calledWith(org.id).mockResolvedValue(null); + await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`); + expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( + "This is the home screen!", + ); + }); + + it.each([ + ProductTierType.Free, + ProductTierType.Families, + ProductTierType.Teams, + ProductTierType.TeamsStarter, + ])( + "shows a dialog to users of a not enterprise organization and does not proceed with navigation for productTierType '%s'", + async (productTierType) => { + const org = orgFactory({ + type: OrganizationUserType.User, + productTierType: productTierType, + }); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`); + expect(dialogService.openSimpleDialog).toHaveBeenCalled(); + expect( + routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "", + ).not.toBe("This component can only be accessed by a enterprise organization!"); + }, + ); + + it("redirects users with billing access to the billing screen to upgrade", async () => { + const org = orgFactory({ + type: OrganizationUserType.Owner, + productTierType: ProductTierType.Teams, + }); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + dialogService.openSimpleDialog.calledWith(any()).mockResolvedValue(true); + await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`); + expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( + "This is the organization upgrade screen!", + ); + }); + + it("proceeds with navigation if the organization in question is a enterprise organization", async () => { + const org = orgFactory({ productTierType: ProductTierType.Enterprise }); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`); + expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( + "This component can only be accessed by a enterprise organization!", + ); + }); +}); diff --git a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.ts b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.ts index 8e35c60db96..3373f0cfd53 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.ts @@ -1,44 +1,45 @@ -import { Injectable } from "@angular/core"; -import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router"; -import { firstValueFrom } from "rxjs"; +import { inject } from "@angular/core"; +import { + ActivatedRouteSnapshot, + CanActivateFn, + Router, + RouterStateSnapshot, +} from "@angular/router"; +import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { DialogService } from "@bitwarden/components"; -@Injectable({ - providedIn: "root", -}) -export class IsEnterpriseOrgGuard implements CanActivate { - constructor( - private router: Router, - private organizationService: OrganizationService, - private dialogService: DialogService, - private configService: ConfigService, - ) {} +/** + * `CanActivateFn` that checks if the organization matching the id in the URL + * parameters is of enterprise type. If the organization is not enterprise instructions are + * provided on how to upgrade into an enterprise organization, and the user is redirected + * if they have access to upgrade the organization. If the organization is + * enterprise routing proceeds." + */ +export function isEnterpriseOrgGuard(): CanActivateFn { + return async (route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => { + const router = inject(Router); + const organizationService = inject(OrganizationService); + const dialogService = inject(DialogService); - async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { - const isMemberAccessReportEnabled = await firstValueFrom( - this.configService.getFeatureFlag$(FeatureFlag.MemberAccessReport), - ); - - // TODO: Remove on "MemberAccessReport" feature flag cleanup - if (!isMemberAccessReportEnabled) { - return this.router.createUrlTree(["/"]); - } - - const org = await this.organizationService.get(route.params.organizationId); + const org = await organizationService.get(route.params.organizationId); if (org == null) { - return this.router.createUrlTree(["/"]); + return router.createUrlTree(["/"]); + } + + // TODO: Remove on "MemberAccessReport" feature flag cleanup + if (!canAccessFeature(FeatureFlag.MemberAccessReport)) { + return router.createUrlTree(["/"]); } if (org.productTierType != ProductTierType.Enterprise) { // Users without billing permission can't access billing if (!org.canEditSubscription) { - await this.dialogService.openSimpleDialog({ + await dialogService.openSimpleDialog({ title: { key: "upgradeOrganizationEnterprise" }, content: { key: "onlyAvailableForEnterpriseOrganization" }, acceptButtonText: { key: "ok" }, @@ -47,7 +48,7 @@ export class IsEnterpriseOrgGuard implements CanActivate { }); return false; } else { - const upgradeConfirmed = await this.dialogService.openSimpleDialog({ + const upgradeConfirmed = await dialogService.openSimpleDialog({ title: { key: "upgradeOrganizationEnterprise" }, content: { key: "onlyAvailableForEnterpriseOrganization" }, acceptButtonText: { key: "upgradeOrganization" }, @@ -55,13 +56,13 @@ export class IsEnterpriseOrgGuard implements CanActivate { icon: "bwi-arrow-circle-up", }); if (upgradeConfirmed) { - await this.router.navigate(["organizations", org.id, "billing", "subscription"], { - queryParams: { upgrade: true }, + await router.navigate(["organizations", org.id, "billing", "subscription"], { + queryParams: { upgrade: true, productTierType: ProductTierType.Enterprise }, }); } } } return org.productTierType == ProductTierType.Enterprise; - } + }; } diff --git a/apps/web/src/app/admin-console/organizations/tools/unsecured-websites-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/unsecured-websites-report.component.ts index 559d2f417a5..c520d3dad68 100644 --- a/apps/web/src/app/admin-console/organizations/tools/unsecured-websites-report.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/unsecured-websites-report.component.ts @@ -5,6 +5,7 @@ import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -29,6 +30,7 @@ export class UnsecuredWebsitesReportComponent passwordRepromptService: PasswordRepromptService, i18nService: I18nService, syncService: SyncService, + collectionService: CollectionService, ) { super( cipherService, @@ -37,6 +39,7 @@ export class UnsecuredWebsitesReportComponent passwordRepromptService, i18nService, syncService, + collectionService, ); } diff --git a/apps/web/src/app/auth/two-factor-auth-duo.component.ts b/apps/web/src/app/auth/two-factor-auth-duo.component.ts new file mode 100644 index 00000000000..0163f8474dd --- /dev/null +++ b/apps/web/src/app/auth/two-factor-auth-duo.component.ts @@ -0,0 +1,60 @@ +import { DialogModule } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { ReactiveFormsModule, FormsModule } from "@angular/forms"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; + +import { TwoFactorAuthDuoComponent as TwoFactorAuthDuoBaseComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component"; +import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions"; +import { ButtonModule } from "../../../../../libs/components/src/button"; +import { FormFieldModule } from "../../../../../libs/components/src/form-field"; +import { LinkModule } from "../../../../../libs/components/src/link"; +import { TypographyModule } from "../../../../../libs/components/src/typography"; + +@Component({ + standalone: true, + selector: "app-two-factor-auth-duo", + templateUrl: + "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.html", + imports: [ + CommonModule, + JslibModule, + DialogModule, + ButtonModule, + LinkModule, + TypographyModule, + ReactiveFormsModule, + FormFieldModule, + AsyncActionsModule, + FormsModule, + ], + providers: [I18nPipe], +}) +export class TwoFactorAuthDuoComponent extends TwoFactorAuthDuoBaseComponent { + async ngOnInit(): Promise { + await super.ngOnInit(); + } + + private duoResultChannel: BroadcastChannel; + + protected override setupDuoResultListener() { + if (!this.duoResultChannel) { + this.duoResultChannel = new BroadcastChannel("duoResult"); + this.duoResultChannel.addEventListener("message", this.handleDuoResultMessage); + } + } + + private handleDuoResultMessage = async (msg: { data: { code: string; state: string } }) => { + this.token.emit(msg.data.code + "|" + msg.data.state); + }; + + async ngOnDestroy() { + if (this.duoResultChannel) { + // clean up duo listener if it was initialized. + this.duoResultChannel.removeEventListener("message", this.handleDuoResultMessage); + this.duoResultChannel.close(); + } + } +} diff --git a/apps/web/src/app/auth/two-factor-auth.component.ts b/apps/web/src/app/auth/two-factor-auth.component.ts index 352d9357288..fbdddecce98 100644 --- a/apps/web/src/app/auth/two-factor-auth.component.ts +++ b/apps/web/src/app/auth/two-factor-auth.component.ts @@ -34,6 +34,8 @@ import { AsyncActionsModule } from "../../../../../libs/components/src/async-act import { ButtonModule } from "../../../../../libs/components/src/button"; import { FormFieldModule } from "../../../../../libs/components/src/form-field"; +import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; + @Component({ standalone: true, templateUrl: @@ -55,6 +57,7 @@ import { FormFieldModule } from "../../../../../libs/components/src/form-field"; TwoFactorAuthEmailComponent, TwoFactorAuthAuthenticatorComponent, TwoFactorAuthYubikeyComponent, + TwoFactorAuthDuoComponent, TwoFactorAuthWebAuthnComponent, ], providers: [I18nPipe], diff --git a/apps/web/src/app/billing/organizations/change-plan.component.html b/apps/web/src/app/billing/organizations/change-plan.component.html index a25dde4fd30..75a12122d19 100644 --- a/apps/web/src/app/billing/organizations/change-plan.component.html +++ b/apps/web/src/app/billing/organizations/change-plan.component.html @@ -18,6 +18,7 @@ [showCancel]="true" [organizationId]="organizationId" [currentPlan]="currentPlan" + [preSelectedProductTier]="preSelectedProductTier" (onCanceled)="cancel()" > diff --git a/apps/web/src/app/billing/organizations/change-plan.component.ts b/apps/web/src/app/billing/organizations/change-plan.component.ts index a131e344b7a..51cdbba557e 100644 --- a/apps/web/src/app/billing/organizations/change-plan.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan.component.ts @@ -1,5 +1,6 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { ProductTierType } from "@bitwarden/common/billing/enums"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -10,6 +11,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" export class ChangePlanComponent { @Input() organizationId: string; @Input() currentPlan: PlanResponse; + @Input() preSelectedProductTier: ProductTierType; @Output() onChanged = new EventEmitter(); @Output() onCanceled = new EventEmitter(); diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index 661b8969136..4f8fbea94d2 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -95,6 +95,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private _plan = PlanType.Free; @Input() providerId?: string; + @Input() preSelectedProductTier?: ProductTierType; @Output() onSuccess = new EventEmitter(); @Output() onCanceled = new EventEmitter(); @Output() onTrialBillingSuccess = new EventEmitter(); @@ -209,6 +210,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.singleOrgPolicyAppliesToActiveUser = policyAppliesToActiveUser; }); + if (this.preSelectedProductTier != null && this.productTier < this.preSelectedProductTier) { + this.productTier = this.preSelectedProductTier; + } if (!this.selfHosted) { this.changedProduct(); } diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html index e519b8887b1..dff6cc5c611 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html @@ -1,6 +1,6 @@ - + {{ "loading" | i18n }} @@ -122,6 +122,7 @@ - +
diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index 8b3225882b0..20c38ec94fa 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -50,8 +50,9 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy locale: string; showUpdatedSubscriptionStatusSection$: Observable; manageBillingFromProviderPortal = ManageBilling; - isProviderManaged = false; + isManagedByConsolidatedBillingMSP = false; enableTimeThreshold: boolean; + preSelectedProductTier: ProductTierType = ProductTierType.Free; protected readonly teamsStarter = ProductTierType.TeamsStarter; @@ -83,6 +84,13 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.changePlan(); + const productTierTypeStr = this.route.snapshot.queryParamMap.get("productTierType"); + if (productTierTypeStr != null) { + const productTier = Number(productTierTypeStr); + if (Object.values(ProductTierType).includes(productTier as ProductTierType)) { + this.preSelectedProductTier = productTier; + } + } } this.route.params @@ -118,10 +126,10 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy if (this.userOrg.canViewSubscription) { const enableConsolidatedBilling = await firstValueFrom(this.enableConsolidatedBilling$); const provider = await this.providerService.get(this.userOrg.providerId); - this.isProviderManaged = + this.isManagedByConsolidatedBillingMSP = enableConsolidatedBilling && this.userOrg.hasProvider && - provider.providerStatus == ProviderStatusType.Billable; + provider?.providerStatus == ProviderStatusType.Billable; this.sub = await this.organizationApiService.getSubscription(this.organizationId); this.lineItems = this.sub?.subscription?.items; diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index a0e0448eef0..a4172ad03ea 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -18,6 +18,7 @@ import { RegistrationStartSecondaryComponent, RegistrationStartSecondaryComponentData, LockIcon, + RegistrationLinkExpiredComponent, } from "@bitwarden/auth/angular"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -205,6 +206,22 @@ const routes: Routes = [ }, ], }, + { + path: "signup-link-expired", + canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()], + data: { + pageTitle: "expiredLink", + } satisfies AnonLayoutWrapperData, + children: [ + { + path: "", + component: RegistrationLinkExpiredComponent, + data: { + loginRoute: "/login", + } satisfies RegistrationStartSecondaryComponentData, + }, + ], + }, { path: "sso", canActivate: [unauthGuardFn()], diff --git a/apps/web/src/app/tools/reports/pages/breach-report.component.html b/apps/web/src/app/tools/reports/pages/breach-report.component.html index 51191ddf2d0..f975d9fd2af 100644 --- a/apps/web/src/app/tools/reports/pages/breach-report.component.html +++ b/apps/web/src/app/tools/reports/pages/breach-report.component.html @@ -2,26 +2,17 @@

{{ "breachDesc" | i18n }}

- -
-
- - - {{ "breachCheckUsernameEmail" | i18n }} -
-
- -
+

{{ "reportError" | i18n }}...

diff --git a/apps/web/src/app/tools/reports/pages/breach-report.component.spec.ts b/apps/web/src/app/tools/reports/pages/breach-report.component.spec.ts new file mode 100644 index 00000000000..5df2ffd0f27 --- /dev/null +++ b/apps/web/src/app/tools/reports/pages/breach-report.component.spec.ts @@ -0,0 +1,128 @@ +// eslint-disable-next-line no-restricted-imports +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ReactiveFormsModule } from "@angular/forms"; +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; +import { AuditService } from "@bitwarden/common/abstractions/audit.service"; +import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BreachAccountResponse } from "@bitwarden/common/models/response/breach-account.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { UserId } from "@bitwarden/common/types/guid"; + +import { BreachReportComponent } from "./breach-report.component"; + +const breachedAccounts = [ + new BreachAccountResponse({ + addedDate: "2021-01-01", + breachDate: "2021-01-01", + dataClasses: ["test"], + description: "test", + domain: "test.com", + isActive: true, + isVerified: true, + logoPath: "test", + modifiedDate: "2021-01-01", + name: "test", + pwnCount: 1, + title: "test", + }), +]; + +describe("BreachReportComponent", () => { + let component: BreachReportComponent; + let fixture: ComponentFixture; + let auditService: MockProxy; + let accountService: MockProxy; + const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ + id: "testId" as UserId, + email: "test@example.com", + emailVerified: true, + name: "Test User", + }); + + beforeEach(async () => { + auditService = mock(); + accountService = mock(); + accountService.activeAccount$ = activeAccountSubject; + + await TestBed.configureTestingModule({ + declarations: [BreachReportComponent, I18nPipe], + imports: [ReactiveFormsModule], + providers: [ + { + provide: AuditService, + useValue: auditService, + }, + { + provide: AccountService, + useValue: accountService, + }, + { + provide: I18nService, + useValue: mock(), + }, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(BreachReportComponent); + component = fixture.componentInstance as BreachReportComponent; + fixture.detectChanges(); + jest.clearAllMocks(); + }); + + it("should initialize component", () => { + expect(component).toBeTruthy(); + }); + + it("should initialize form with account email", async () => { + expect(component.formGroup.get("username").value).toEqual("test@example.com"); + }); + + it("should mark form as touched and show validation error if form is invalid on submit", async () => { + component.formGroup.get("username").setValue(""); + await component.submit(); + + expect(component.formGroup.touched).toBe(true); + expect(component.formGroup.invalid).toBe(true); + }); + + it("should call auditService.breachedAccounts with lowercase username", async () => { + auditService.breachedAccounts.mockResolvedValue(breachedAccounts); + component.formGroup.get("username").setValue("validUsername"); + + await component.submit(); + + expect(auditService.breachedAccounts).toHaveBeenCalledWith("validusername"); + }); + + it("should set breachedAccounts and checkedUsername after successful submit", async () => { + auditService.breachedAccounts.mockResolvedValue(breachedAccounts); + + await component.submit(); + + expect(component.breachedAccounts).toEqual(breachedAccounts); + expect(component.checkedUsername).toEqual("test@example.com"); + }); + + it("should set error to true if auditService.breachedAccounts throws an error", async () => { + auditService.breachedAccounts.mockRejectedValue(new Error("test error")); + component.formGroup.get("username").setValue("validUsername"); + + await component.submit(); + + expect(component.error).toBe(true); + }); + + it("should set loading to false after submit", async () => { + auditService.breachedAccounts.mockResolvedValue([]); + component.formGroup.get("username").setValue("validUsername"); + + await component.submit(); + + expect(component.loading).toBe(false); + }); +}); diff --git a/apps/web/src/app/tools/reports/pages/breach-report.component.ts b/apps/web/src/app/tools/reports/pages/breach-report.component.ts index 5728d360785..177dcac4f2f 100644 --- a/apps/web/src/app/tools/reports/pages/breach-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/breach-report.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; import { firstValueFrom, map } from "rxjs"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; @@ -10,32 +11,46 @@ import { BreachAccountResponse } from "@bitwarden/common/models/response/breach- templateUrl: "breach-report.component.html", }) export class BreachReportComponent implements OnInit { + loading = false; error = false; - username: string; checkedUsername: string; breachedAccounts: BreachAccountResponse[] = []; - formPromise: Promise; + formGroup = this.formBuilder.group({ + username: ["", { validators: [Validators.required], updateOn: "change" }], + }); constructor( private auditService: AuditService, private accountService: AccountService, + private formBuilder: FormBuilder, ) {} async ngOnInit() { - this.username = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.email)), - ); + this.formGroup + .get("username") + .setValue( + await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.email))), + ); } - async submit() { + submit = async () => { + this.formGroup.markAsTouched(); + + if (this.formGroup.invalid) { + return; + } + this.error = false; - this.username = this.username.toLowerCase(); + this.loading = true; + const username = this.formGroup.value.username.toLowerCase(); try { - this.formPromise = this.auditService.breachedAccounts(this.username); - this.breachedAccounts = await this.formPromise; + this.breachedAccounts = await this.auditService.breachedAccounts(username); } catch { this.error = true; + } finally { + this.loading = false; } - this.checkedUsername = this.username; - } + + this.checkedUsername = username; + }; } diff --git a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.html b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.html index 87447e45273..2f4bdd6a1d4 100644 --- a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.html +++ b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.html @@ -11,13 +11,13 @@ {{ "loading" | i18n }}
- + {{ "noUnsecuredWebsites" | i18n }} - + - + {{ "unsecuredWebsitesFoundReportDesc" | i18n: (ciphers.length | number) : vaultMsg }} - + - - + + - - - + + + - -
{{ "name" | i18n }} {{ "owner" | i18n }}
- + - {{ - c.name + {{ + r.name }} - + {{ "shared" | i18n }} - + {{ "attachments" | i18n }}
- {{ c.subTitle }} + {{ r.subTitle }}
+ +
diff --git a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.spec.ts b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.spec.ts index e616d1f21e5..a1f631d2b63 100644 --- a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.spec.ts +++ b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.spec.ts @@ -8,6 +8,7 @@ import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -19,11 +20,13 @@ describe("UnsecuredWebsitesReportComponent", () => { let fixture: ComponentFixture; let organizationService: MockProxy; let syncServiceMock: MockProxy; + let collectionService: MockProxy; beforeEach(() => { organizationService = mock(); organizationService.organizations$ = of([]); syncServiceMock = mock(); + collectionService = mock(); // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises TestBed.configureTestingModule({ @@ -53,6 +56,10 @@ describe("UnsecuredWebsitesReportComponent", () => { provide: I18nService, useValue: mock(), }, + { + provide: CollectionService, + useValue: collectionService, + }, ], schemas: [], }).compileComponents(); diff --git a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts index 0a8023c3031..b2b8ce298a3 100644 --- a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts @@ -4,8 +4,11 @@ import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { Collection } from "@bitwarden/common/vault/models/domain/collection"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { PasswordRepromptService } from "@bitwarden/vault"; import { CipherReportComponent } from "./cipher-report.component"; @@ -24,6 +27,7 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl passwordRepromptService: PasswordRepromptService, i18nService: I18nService, syncService: SyncService, + private collectionService: CollectionService, ) { super( cipherService, @@ -41,15 +45,51 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl async setCiphers() { const allCiphers = await this.getAllCiphers(); + const allCollections = await this.collectionService.getAll(); this.filterStatus = [0]; + const unsecuredCiphers = allCiphers.filter((c) => { - if (c.type !== CipherType.Login || !c.login.hasUris || c.isDeleted) { + const containsUnsecured = this.cipherContainsUnsecured(c); + if (containsUnsecured === false) { return false; } - return c.login.uris.some((u: any) => u.uri != null && u.uri.indexOf("http://") === 0); + const canView = this.canView(c, allCollections); + return canView; }); this.filterCiphersByOrg(unsecuredCiphers); } + + /** + * Cipher needs to be a Login type, contain Uris, and not be deleted + * @param cipher Current cipher with unsecured uri + */ + private cipherContainsUnsecured(cipher: CipherView): boolean { + if (cipher.type !== CipherType.Login || !cipher.login.hasUris || cipher.isDeleted) { + return false; + } + + const containsUnsecured = cipher.login.uris.some( + (u: any) => u.uri != null && u.uri.indexOf("http://") === 0, + ); + return containsUnsecured; + } + + /** + * If the user does not have readonly set or it's false they have the ability to edit + * @param cipher Current cipher with unsecured uri + * @param allCollections The collections for the user + */ + private canView(cipher: CipherView, allCollections: Collection[]): boolean { + if (!cipher.organizationId) { + return true; + } + + return ( + allCollections.filter( + (item) => cipher.collectionIds.indexOf(item.id) > -1 && !(item.readOnly ?? false), + ).length > 0 + ); + } } diff --git a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts index 734b7179acd..fe61d9b9a11 100644 --- a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts @@ -8,7 +8,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "app-folder-add-edit", @@ -24,6 +24,7 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { logService: LogService, dialogService: DialogService, formBuilder: FormBuilder, + protected toastService: ToastService, protected dialogRef: DialogRef, @Inject(DIALOG_DATA) params: FolderAddEditDialogParams, ) { @@ -51,10 +52,12 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { } try { - this.deletePromise = this.folderApiService.delete(this.folder.id); - await this.deletePromise; - this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedFolder")); - this.onDeletedFolder.emit(this.folder); + await this.folderApiService.delete(this.folder.id); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedFolder"), + }); } catch (e) { this.logService.error(e); } diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts index 8c637d22b17..5fdac63e932 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts @@ -1,5 +1,6 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; +import { RouterTestingModule } from "@angular/router/testing"; import { mock } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; @@ -11,7 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { BannerComponent, BannerModule } from "@bitwarden/components"; import { VerifyEmailComponent } from "../../../auth/settings/verify-email.component"; -import { LooseComponentsModule } from "../../../shared"; +import { SharedModule } from "../../../shared"; import { VaultBannersService, VisibleVaultBanner } from "./services/vault-banners.service"; import { VaultBannersComponent } from "./vault-banners.component"; @@ -36,13 +37,15 @@ describe("VaultBannersComponent", () => { bannerService.shouldShowLowKDFBanner.mockResolvedValue(false); await TestBed.configureTestingModule({ - imports: [BannerModule, LooseComponentsModule, VerifyEmailComponent], - declarations: [VaultBannersComponent, I18nPipe], + imports: [ + BannerModule, + SharedModule, + VerifyEmailComponent, + VaultBannersComponent, + RouterTestingModule, + ], + declarations: [I18nPipe], providers: [ - { - provide: VaultBannersService, - useValue: bannerService, - }, { provide: I18nService, useValue: mock({ t: (key) => key }), @@ -60,7 +63,9 @@ describe("VaultBannersComponent", () => { useValue: mock(), }, ], - }).compileComponents(); + }) + .overrideProvider(VaultBannersService, { useValue: bannerService }) + .compileComponents(); }); beforeEach(() => { diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts index e612bc231da..5a53f57ad19 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts @@ -1,11 +1,19 @@ import { Component, OnInit } from "@angular/core"; import { Observable } from "rxjs"; +import { BannerModule } from "@bitwarden/components"; + +import { VerifyEmailComponent } from "../../../auth/settings/verify-email.component"; +import { SharedModule } from "../../../shared"; + import { VaultBannersService, VisibleVaultBanner } from "./services/vault-banners.service"; @Component({ + standalone: true, selector: "app-vault-banners", templateUrl: "./vault-banners.component.html", + imports: [VerifyEmailComponent, SharedModule, BannerModule], + providers: [VaultBannersService], }) export class VaultBannersComponent implements OnInit { visibleBanners: VisibleVaultBanner[] = []; diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts index 9e69286277d..7803b1c32f2 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts @@ -1,3 +1,4 @@ +import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, Component, @@ -8,14 +9,19 @@ import { } from "@angular/core"; import { firstValueFrom } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; +import { BreadcrumbsModule, MenuModule } from "@bitwarden/components"; +import { HeaderModule } from "../../../layouts/header/header.module"; +import { SharedModule } from "../../../shared"; import { CollectionDialogTabType } from "../../components/collection-dialog"; +import { PipesModule } from "../pipes/pipes.module"; import { All, RoutedVaultFilterModel, @@ -23,8 +29,18 @@ import { } from "../vault-filter/shared/models/routed-vault-filter.model"; @Component({ + standalone: true, selector: "app-vault-header", templateUrl: "./vault-header.component.html", + imports: [ + CommonModule, + MenuModule, + SharedModule, + BreadcrumbsModule, + HeaderModule, + PipesModule, + JslibModule, + ], changeDetection: ChangeDetectionStrategy.OnPush, }) export class VaultHeaderComponent implements OnInit { diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts index 6c451344c9f..490c07d7538 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts @@ -158,10 +158,9 @@ describe("VaultOnboardingComponent", () => { }); it("should set installExtension to true when hasBWInstalled command is passed", async () => { - const saveCompletedTasksSpy = jest.spyOn( - (component as any).vaultOnboardingService, - "setVaultOnboardingTasks", - ); + const saveCompletedTasksSpy = jest + .spyOn((component as any).vaultOnboardingService, "setVaultOnboardingTasks") + .mockReturnValue(Promise.resolve()); (component as any).vaultOnboardingService.vaultOnboardingState$ = of({ createAccount: true, diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts index 106f5b0d70d..a7331c73151 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts @@ -24,11 +24,17 @@ import { LinkModule } from "@bitwarden/components"; import { OnboardingModule } from "../../../shared/components/onboarding/onboarding.module"; import { VaultOnboardingService as VaultOnboardingServiceAbstraction } from "./services/abstraction/vault-onboarding.service"; -import { VaultOnboardingTasks } from "./services/vault-onboarding.service"; +import { VaultOnboardingService, VaultOnboardingTasks } from "./services/vault-onboarding.service"; @Component({ standalone: true, imports: [OnboardingModule, CommonModule, JslibModule, LinkModule], + providers: [ + { + provide: VaultOnboardingServiceAbstraction, + useClass: VaultOnboardingService, + }, + ], selector: "app-vault-onboarding", templateUrl: "vault-onboarding.component.html", }) diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 7d17141e591..71c025f276a 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -60,6 +60,7 @@ import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; import { DialogService, Icons, ToastService } from "@bitwarden/components"; import { CollectionAssignmentResult, PasswordRepromptService } from "@bitwarden/vault"; +import { SharedModule } from "../../shared/shared.module"; import { AssignCollectionsWebComponent } from "../components/assign-collections"; import { CollectionDialogAction, @@ -68,6 +69,7 @@ import { } from "../components/collection-dialog"; import { VaultItem } from "../components/vault-items/vault-item"; import { VaultItemEvent } from "../components/vault-items/vault-item-event"; +import { VaultItemsModule } from "../components/vault-items/vault-items.module"; import { getNestedCollectionTree } from "../utils/collection-utils"; import { AddEditComponent } from "./add-edit.component"; @@ -87,6 +89,7 @@ import { import { openIndividualVaultCollectionsDialog } from "./collections.component"; import { FolderAddEditDialogResult, openFolderAddEditDialog } from "./folder-add-edit.component"; import { ShareComponent } from "./share.component"; +import { VaultBannersComponent } from "./vault-banners/vault-banners.component"; import { VaultFilterComponent } from "./vault-filter/components/vault-filter.component"; import { VaultFilterService } from "./vault-filter/services/abstractions/vault-filter.service"; import { RoutedVaultFilterBridgeService } from "./vault-filter/services/routed-vault-filter-bridge.service"; @@ -99,13 +102,25 @@ import { } from "./vault-filter/shared/models/routed-vault-filter.model"; import { VaultFilter } from "./vault-filter/shared/models/vault-filter.model"; import { FolderFilter, OrganizationFilter } from "./vault-filter/shared/models/vault-filter.type"; +import { VaultFilterModule } from "./vault-filter/vault-filter.module"; +import { VaultHeaderComponent } from "./vault-header/vault-header.component"; +import { VaultOnboardingComponent } from "./vault-onboarding/vault-onboarding.component"; const BroadcasterSubscriptionId = "VaultComponent"; const SearchTextDebounceInterval = 200; @Component({ + standalone: true, selector: "app-vault", templateUrl: "vault.component.html", + imports: [ + VaultHeaderComponent, + VaultOnboardingComponent, + VaultBannersComponent, + VaultFilterModule, + VaultItemsModule, + SharedModule, + ], providers: [RoutedVaultFilterService, RoutedVaultFilterBridgeService], }) export class VaultComponent implements OnInit, OnDestroy { @@ -323,18 +338,14 @@ export class VaultComponent implements OnInit, OnDestroy { const cipherId = getCipherIdFromParams(params); if (cipherId) { if ((await this.cipherService.get(cipherId)) != null) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.editCipherId(cipherId); + await this.editCipherId(cipherId); } else { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("unknownCipher"), - ); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([], { + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("unknownCipher"), + }); + await this.router.navigate([], { queryParams: { itemId: null, cipherId: null }, queryParamsHandling: "merge", }); @@ -403,36 +414,48 @@ export class VaultComponent implements OnInit, OnDestroy { async onVaultItemsEvent(event: VaultItemEvent) { this.processingEvent = true; try { - if (event.type === "viewAttachments") { - await this.editCipherAttachments(event.item); - } else if (event.type === "viewCipherCollections") { - await this.editCipherCollections(event.item); - } else if (event.type === "clone") { - await this.cloneCipher(event.item); - } else if (event.type === "restore") { - if (event.items.length === 1) { - await this.restore(event.items[0]); - } else { - await this.bulkRestore(event.items); - } - } else if (event.type === "delete") { - await this.handleDeleteEvent(event.items); - } else if (event.type === "moveToFolder") { - await this.bulkMove(event.items); - } else if (event.type === "moveToOrganization") { - if (event.items.length === 1) { - await this.shareCipher(event.items[0]); - } else { - await this.bulkShare(event.items); - } - } else if (event.type === "copyField") { - await this.copy(event.item, event.field); - } else if (event.type === "editCollection") { - await this.editCollection(event.item, CollectionDialogTabType.Info); - } else if (event.type === "viewCollectionAccess") { - await this.editCollection(event.item, CollectionDialogTabType.Access); - } else if (event.type === "assignToCollections") { - await this.bulkAssignToCollections(event.items); + switch (event.type) { + case "viewAttachments": + await this.editCipherAttachments(event.item); + break; + case "viewCipherCollections": + await this.editCipherCollections(event.item); + break; + case "clone": + await this.cloneCipher(event.item); + break; + case "restore": + if (event.items.length === 1) { + await this.restore(event.items[0]); + } else { + await this.bulkRestore(event.items); + } + break; + case "delete": + await this.handleDeleteEvent(event.items); + break; + case "moveToFolder": + await this.bulkMove(event.items); + break; + case "moveToOrganization": + if (event.items.length === 1) { + await this.shareCipher(event.items[0]); + } else { + await this.bulkShare(event.items); + } + break; + case "copyField": + await this.copy(event.item, event.field); + break; + case "editCollection": + await this.editCollection(event.item, CollectionDialogTabType.Info); + break; + case "viewCollectionAccess": + await this.editCollection(event.item, CollectionDialogTabType.Access); + break; + case "assignToCollections": + await this.bulkAssignToCollections(event.items); + break; } } finally { this.processingEvent = false; @@ -445,9 +468,7 @@ export class VaultComponent implements OnInit, OnDestroy { } const orgs = await firstValueFrom(this.filterComponent.filters.organizationFilter.data$); const orgNode = ServiceUtils.getTreeNodeObject(orgs, orgId) as TreeNode; - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.filterComponent.filters?.organizationFilter?.action(orgNode); + await this.filterComponent.filters?.organizationFilter?.action(orgNode); } addFolder = async (): Promise => { @@ -464,9 +485,7 @@ export class VaultComponent implements OnInit, OnDestroy { const result = await lastValueFrom(dialog.closed); if (result === FolderAddEditDialogResult.Deleted) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([], { + await this.router.navigate([], { queryParams: { folderId: null }, queryParamsHandling: "merge", replaceUrl: true, @@ -672,7 +691,7 @@ export class VaultComponent implements OnInit, OnDestroy { this.refresh(); // Navigate away if we deleted the collection we were viewing if (this.selectedCollection?.node.id === c?.id) { - void this.router.navigate([], { + await this.router.navigate([], { queryParams: { collectionId: this.selectedCollection.parent?.node.id ?? null }, queryParamsHandling: "merge", replaceUrl: true, @@ -699,14 +718,15 @@ export class VaultComponent implements OnInit, OnDestroy { try { await this.apiService.deleteCollection(collection.organizationId, collection.id); await this.collectionService.delete(collection.id); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("deletedCollectionId", collection.name), - ); + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedCollectionId", collection.name), + }); // Navigate away if we deleted the collection we were viewing if (this.selectedCollection?.node.id === collection.id) { - void this.router.navigate([], { + await this.router.navigate([], { queryParams: { collectionId: this.selectedCollection.parent?.node.id ?? null }, queryParamsHandling: "merge", replaceUrl: true, @@ -724,11 +744,11 @@ export class VaultComponent implements OnInit, OnDestroy { } if (ciphers.length === 0) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("nothingSelected"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("nothingSelected"), + }); return; } @@ -792,7 +812,11 @@ export class VaultComponent implements OnInit, OnDestroy { try { await this.cipherService.restoreWithServer(c.id); - this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("restoredItem"), + }); this.refresh(); } catch (e) { this.logService.error(e); @@ -811,12 +835,20 @@ export class VaultComponent implements OnInit, OnDestroy { const selectedCipherIds = ciphers.map((cipher) => cipher.id); if (selectedCipherIds.length === 0) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("nothingSelected"), + }); return; } await this.cipherService.restoreManyWithServer(selectedCipherIds); - this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItems")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("restoredItems"), + }); this.refresh(); } @@ -864,11 +896,12 @@ export class VaultComponent implements OnInit, OnDestroy { try { await this.deleteCipherWithServer(c.id, permanent); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem"), - ); + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem"), + }); this.refresh(); } catch (e) { this.logService.error(e); @@ -885,7 +918,11 @@ export class VaultComponent implements OnInit, OnDestroy { } if (ciphers.length === 0 && collections.length === 0) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("nothingSelected"), + }); return; } @@ -928,7 +965,11 @@ export class VaultComponent implements OnInit, OnDestroy { const selectedCipherIds = ciphers.map((cipher) => cipher.id); if (selectedCipherIds.length === 0) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("nothingSelected"), + }); return; } @@ -960,7 +1001,11 @@ export class VaultComponent implements OnInit, OnDestroy { value = await this.totpService.getCode(cipher.login.totp); typeI18nKey = "verificationCodeTotp"; } else { - this.platformUtilsService.showToast("info", null, this.i18nService.t("unexpectedError")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("unexpectedError"), + }); return; } @@ -976,20 +1021,19 @@ export class VaultComponent implements OnInit, OnDestroy { } this.platformUtilsService.copyToClipboard(value, { window: window }); - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), - ); + this.toastService.showToast({ + variant: "info", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), + }); if (field === "password") { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); + await this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); } else if (field === "totp") { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect(EventType.Cipher_ClientCopiedHiddenField, cipher.id); + await this.eventCollectionService.collect( + EventType.Cipher_ClientCopiedHiddenField, + cipher.id, + ); } } @@ -1008,7 +1052,11 @@ export class VaultComponent implements OnInit, OnDestroy { } if (ciphers.length === 0) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("nothingSelected"), + }); return; } @@ -1058,9 +1106,7 @@ export class VaultComponent implements OnInit, OnDestroy { }; } - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([], { + void this.router.navigate([], { relativeTo: this.route, queryParams: queryParams, queryParamsHandling: "merge", diff --git a/apps/web/src/app/vault/individual-vault/vault.module.ts b/apps/web/src/app/vault/individual-vault/vault.module.ts index c79c64c1ebf..a6cb41bacb8 100644 --- a/apps/web/src/app/vault/individual-vault/vault.module.ts +++ b/apps/web/src/app/vault/individual-vault/vault.module.ts @@ -1,30 +1,18 @@ import { NgModule } from "@angular/core"; -import { BannerModule, BreadcrumbsModule } from "@bitwarden/components"; - -import { VerifyEmailComponent } from "../../auth/settings/verify-email.component"; import { LooseComponentsModule, SharedModule } from "../../shared"; import { CollectionDialogModule } from "../components/collection-dialog"; -import { VaultItemsModule } from "../components/vault-items/vault-items.module"; import { CollectionBadgeModule } from "../org-vault/collection-badge/collection-badge.module"; import { GroupBadgeModule } from "../org-vault/group-badge/group-badge.module"; import { BulkDialogsModule } from "./bulk-action-dialogs/bulk-dialogs.module"; import { OrganizationBadgeModule } from "./organization-badge/organization-badge.module"; import { PipesModule } from "./pipes/pipes.module"; -import { VaultBannersService } from "./vault-banners/services/vault-banners.service"; -import { VaultBannersComponent } from "./vault-banners/vault-banners.component"; -import { VaultFilterModule } from "./vault-filter/vault-filter.module"; -import { VaultHeaderComponent } from "./vault-header/vault-header.component"; -import { VaultOnboardingService as VaultOnboardingServiceAbstraction } from "./vault-onboarding/services/abstraction/vault-onboarding.service"; -import { VaultOnboardingService } from "./vault-onboarding/services/vault-onboarding.service"; -import { VaultOnboardingComponent } from "./vault-onboarding/vault-onboarding.component"; import { VaultRoutingModule } from "./vault-routing.module"; import { VaultComponent } from "./vault.component"; @NgModule({ imports: [ - VaultFilterModule, VaultRoutingModule, OrganizationBadgeModule, GroupBadgeModule, @@ -33,21 +21,8 @@ import { VaultComponent } from "./vault.component"; SharedModule, LooseComponentsModule, BulkDialogsModule, - BreadcrumbsModule, - VaultItemsModule, CollectionDialogModule, - VaultOnboardingComponent, - BannerModule, - VerifyEmailComponent, - ], - declarations: [VaultComponent, VaultHeaderComponent, VaultBannersComponent], - exports: [VaultComponent], - providers: [ - VaultBannersService, - { - provide: VaultOnboardingServiceAbstraction, - useClass: VaultOnboardingService, - }, + VaultComponent, ], }) export class VaultModule {} diff --git a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts index 081d1e503e8..31764fcf058 100644 --- a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts @@ -1,7 +1,9 @@ +import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { ProductTierType } from "@bitwarden/common/billing/enums"; @@ -9,8 +11,16 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { DialogService, SimpleDialogOptions } from "@bitwarden/components"; +import { + DialogService, + SimpleDialogOptions, + BreadcrumbsModule, + MenuModule, + SearchModule, +} from "@bitwarden/components"; +import { HeaderModule } from "../../../layouts/header/header.module"; +import { SharedModule } from "../../../shared"; import { CollectionAdminView } from "../../../vault/core/views/collection-admin.view"; import { CollectionDialogTabType } from "../../components/collection-dialog"; import { CollectionAdminService } from "../../core/collection-admin.service"; @@ -21,8 +31,18 @@ import { } from "../../individual-vault/vault-filter/shared/models/routed-vault-filter.model"; @Component({ + standalone: true, selector: "app-org-vault-header", templateUrl: "./vault-header.component.html", + imports: [ + CommonModule, + MenuModule, + SharedModule, + BreadcrumbsModule, + HeaderModule, + SearchModule, + JslibModule, + ], }) export class VaultHeaderComponent implements OnInit { protected All = All; diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 07d65656d2f..a89fc14f0db 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -58,11 +58,12 @@ import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; -import { DialogService, Icons, ToastService } from "@bitwarden/components"; +import { DialogService, Icons, NoItemsModule, ToastService } from "@bitwarden/components"; import { CollectionAssignmentResult, PasswordRepromptService } from "@bitwarden/vault"; import { GroupService, GroupView } from "../../admin-console/organizations/core"; import { openEntityEventsDialog } from "../../admin-console/organizations/manage/entity-events.component"; +import { SharedModule } from "../../shared"; import { VaultFilterService } from "../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service"; import { VaultFilter } from "../../vault/individual-vault/vault-filter/shared/models/vault-filter.model"; import { AssignCollectionsWebComponent } from "../components/assign-collections"; @@ -72,6 +73,7 @@ import { openCollectionDialog, } from "../components/collection-dialog"; import { VaultItemEvent } from "../components/vault-items/vault-item-event"; +import { VaultItemsModule } from "../components/vault-items/vault-items.module"; import { CollectionAdminService } from "../core/collection-admin.service"; import { CollectionAdminView } from "../core/views/collection-admin.view"; import { @@ -87,6 +89,7 @@ import { RoutedVaultFilterModel, Unassigned, } from "../individual-vault/vault-filter/shared/models/routed-vault-filter.model"; +import { VaultHeaderComponent } from "../org-vault/vault-header/vault-header.component"; import { getNestedCollectionTree } from "../utils/collection-utils"; import { AddEditComponent } from "./add-edit.component"; @@ -95,7 +98,9 @@ import { BulkCollectionsDialogComponent, BulkCollectionsDialogResult, } from "./bulk-collections-dialog"; +import { CollectionAccessRestrictedComponent } from "./collection-access-restricted.component"; import { openOrgVaultCollectionsDialog } from "./collections.component"; +import { VaultFilterModule } from "./vault-filter/vault-filter.module"; const BroadcasterSubscriptionId = "OrgVaultComponent"; const SearchTextDebounceInterval = 200; @@ -106,8 +111,17 @@ enum AddAccessStatusType { } @Component({ + standalone: true, selector: "app-org-vault", templateUrl: "vault.component.html", + imports: [ + VaultHeaderComponent, + CollectionAccessRestrictedComponent, + VaultFilterModule, + VaultItemsModule, + SharedModule, + NoItemsModule, + ], providers: [RoutedVaultFilterService, RoutedVaultFilterBridgeService], }) export class VaultComponent implements OnInit, OnDestroy { @@ -577,7 +591,11 @@ export class VaultComponent implements OnInit, OnDestroy { if (canEditCipher) { await this.editCipherId(cipherId); } else { - this.platformUtilsService.showToast("error", null, this.i18nService.t("unknownCipher")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("unknownCipher"), + }); await this.router.navigate([], { queryParams: { cipherId: null, itemId: null }, queryParamsHandling: "merge", @@ -598,14 +616,14 @@ export class VaultComponent implements OnInit, OnDestroy { } const cipher = allCiphers$.find((c) => c.id === cipherId); if (organization.useEvents && cipher != undefined) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.viewEvents(cipher); + await this.viewEvents(cipher); } else { - this.platformUtilsService.showToast("error", null, this.i18nService.t("unknownCipher")); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([], { + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("unknownCipher"), + }); + await this.router.navigate([], { queryParams: { viewEvents: null }, queryParamsHandling: "merge", }); @@ -686,50 +704,65 @@ export class VaultComponent implements OnInit, OnDestroy { this.processingEvent = true; try { - if (event.type === "viewAttachments") { - await this.editCipherAttachments(event.item); - } else if (event.type === "viewCipherCollections") { - await this.editCipherCollections(event.item); - } else if (event.type === "clone") { - await this.cloneCipher(event.item); - } else if (event.type === "restore") { - if (event.items.length === 1) { - await this.restore(event.items[0]); - } else { - await this.bulkRestore(event.items); + switch (event.type) { + case "viewAttachments": + await this.editCipherAttachments(event.item); + break; + case "viewCipherCollections": + await this.editCipherCollections(event.item); + break; + case "clone": + await this.cloneCipher(event.item); + break; + case "restore": + if (event.items.length === 1) { + await this.restore(event.items[0]); + } else { + await this.bulkRestore(event.items); + } + break; + case "delete": { + const ciphers = event.items + .filter((i) => i.collection === undefined) + .map((i) => i.cipher); + const collections = event.items + .filter((i) => i.cipher === undefined) + .map((i) => i.collection); + if (ciphers.length === 1 && collections.length === 0) { + await this.deleteCipher(ciphers[0]); + } else if (ciphers.length === 0 && collections.length === 1) { + await this.deleteCollection(collections[0] as CollectionAdminView); + } else { + await this.bulkDelete(ciphers, collections, this.organization); + } + break; } - } else if (event.type === "delete") { - const ciphers = event.items.filter((i) => i.collection === undefined).map((i) => i.cipher); - const collections = event.items - .filter((i) => i.cipher === undefined) - .map((i) => i.collection); - if (ciphers.length === 1 && collections.length === 0) { - await this.deleteCipher(ciphers[0]); - } else if (ciphers.length === 0 && collections.length === 1) { - await this.deleteCollection(collections[0] as CollectionAdminView); - } else { - await this.bulkDelete(ciphers, collections, this.organization); - } - } else if (event.type === "copyField") { - await this.copy(event.item, event.field); - } else if (event.type === "editCollection") { - await this.editCollection( - event.item as CollectionAdminView, - CollectionDialogTabType.Info, - event.readonly, - ); - } else if (event.type === "viewCollectionAccess") { - await this.editCollection( - event.item as CollectionAdminView, - CollectionDialogTabType.Access, - event.readonly, - ); - } else if (event.type === "bulkEditCollectionAccess") { - await this.bulkEditCollectionAccess(event.items, this.organization); - } else if (event.type === "assignToCollections") { - await this.bulkAssignToCollections(event.items); - } else if (event.type === "viewEvents") { - await this.viewEvents(event.item); + case "copyField": + await this.copy(event.item, event.field); + break; + case "editCollection": + await this.editCollection( + event.item as CollectionAdminView, + CollectionDialogTabType.Info, + event.readonly, + ); + break; + case "viewCollectionAccess": + await this.editCollection( + event.item as CollectionAdminView, + CollectionDialogTabType.Access, + event.readonly, + ); + break; + case "bulkEditCollectionAccess": + await this.bulkEditCollectionAccess(event.items, this.organization); + break; + case "assignToCollections": + await this.bulkAssignToCollections(event.items); + break; + case "viewEvents": + await this.viewEvents(event.item); + break; } } finally { this.processingEvent = false; @@ -962,7 +995,11 @@ export class VaultComponent implements OnInit, OnDestroy { this.organization?.canEditAnyCollection(this.flexibleCollectionsV1Enabled) || c.isUnassigned; await this.cipherService.restoreWithServer(c.id, asAdmin); - this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("restoredItem"), + }); this.refresh(); } catch (e) { this.logService.error(e); @@ -1008,11 +1045,11 @@ export class VaultComponent implements OnInit, OnDestroy { } if (unassignedCiphers.length === 0 && editAccessCiphers.length === 0) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("nothingSelected"), - ); + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("nothingSelected"), + }); return; } @@ -1023,7 +1060,11 @@ export class VaultComponent implements OnInit, OnDestroy { ); } - this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItems")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("restoredItems"), + }); this.refresh(); } @@ -1058,11 +1099,11 @@ export class VaultComponent implements OnInit, OnDestroy { try { await this.deleteCipherWithServer(c.id, permanent, c.isUnassigned); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem"), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t(permanent ? "permanentlyDeletedItem" : "deletedItem"), + }); this.refresh(); } catch (e) { this.logService.error(e); @@ -1085,11 +1126,11 @@ export class VaultComponent implements OnInit, OnDestroy { } try { await this.apiService.deleteCollection(this.organization?.id, collection.id); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("deletedCollectionId", collection.name), - ); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedCollectionId", collection.name), + }); // Navigate away if we deleted the collection we were viewing if (this.selectedCollection?.node.id === collection.id) { @@ -1128,7 +1169,11 @@ export class VaultComponent implements OnInit, OnDestroy { }); if (ciphers.length === 0 && collections.length === 0) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("nothingSelected"), + }); return; } @@ -1182,7 +1227,11 @@ export class VaultComponent implements OnInit, OnDestroy { value = await this.totpService.getCode(cipher.login.totp); typeI18nKey = "verificationCodeTotp"; } else { - this.platformUtilsService.showToast("info", null, this.i18nService.t("unexpectedError")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("unexpectedError"), + }); return; } @@ -1198,20 +1247,19 @@ export class VaultComponent implements OnInit, OnDestroy { } this.platformUtilsService.copyToClipboard(value, { window: window }); - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), - ); + this.toastService.showToast({ + variant: "info", + title: null, + message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), + }); if (field === "password") { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); + await this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); } else if (field === "totp") { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect(EventType.Cipher_ClientCopiedHiddenField, cipher.id); + await this.eventCollectionService.collect( + EventType.Cipher_ClientCopiedHiddenField, + cipher.id, + ); } } @@ -1310,7 +1358,11 @@ export class VaultComponent implements OnInit, OnDestroy { async bulkAssignToCollections(items: CipherView[]) { if (items.length === 0) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("nothingSelected")); + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("nothingSelected"), + }); return; } @@ -1381,9 +1433,7 @@ export class VaultComponent implements OnInit, OnDestroy { }; } - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([], { + void this.router.navigate([], { relativeTo: this.route, queryParams: queryParams, queryParamsHandling: "merge", diff --git a/apps/web/src/app/vault/org-vault/vault.module.ts b/apps/web/src/app/vault/org-vault/vault.module.ts index a478307123c..7a874ad612c 100644 --- a/apps/web/src/app/vault/org-vault/vault.module.ts +++ b/apps/web/src/app/vault/org-vault/vault.module.ts @@ -1,40 +1,25 @@ import { NgModule } from "@angular/core"; -import { BreadcrumbsModule, NoItemsModule, SearchModule } from "@bitwarden/components"; - import { LooseComponentsModule } from "../../shared/loose-components.module"; import { SharedModule } from "../../shared/shared.module"; import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module"; -import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; import { CollectionDialogModule } from "../components/collection-dialog"; -import { VaultItemsModule } from "../components/vault-items/vault-items.module"; -import { CollectionAccessRestrictedComponent } from "./collection-access-restricted.component"; import { CollectionBadgeModule } from "./collection-badge/collection-badge.module"; import { GroupBadgeModule } from "./group-badge/group-badge.module"; -import { VaultFilterModule } from "./vault-filter/vault-filter.module"; -import { VaultHeaderComponent } from "./vault-header/vault-header.component"; import { VaultRoutingModule } from "./vault-routing.module"; import { VaultComponent } from "./vault.component"; @NgModule({ imports: [ VaultRoutingModule, - VaultFilterModule, SharedModule, LooseComponentsModule, GroupBadgeModule, CollectionBadgeModule, OrganizationBadgeModule, - PipesModule, - BreadcrumbsModule, - VaultItemsModule, CollectionDialogModule, - CollectionAccessRestrictedComponent, - NoItemsModule, - SearchModule, + VaultComponent, ], - declarations: [VaultComponent, VaultHeaderComponent], - exports: [VaultComponent], }) export class VaultModule {} diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 9809c1cc7f1..2505b698b9b 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Kaarthouernaam" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Nommer" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Waarmerksleutel (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Vouer" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "U aantekensessie het verstryk." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Is u seker u wil uitteken?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "U het niks gekies nie." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Bevestigingskode word vereis." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Ongeldige bevestigingskode" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 6974170dd65..c097deca5cf 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "اسم حامل البطاقة" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "الرقم" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "مفتاح المصادقة (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "المجلد" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "انتهت صَلاحِيَة جَلسة الدخول." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "هل أنت متأكد من أنك تريد تسجيل الخروج؟" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "رمز التحقق مطلوب." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "رمز التحقق غير صالح" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index d49fd446241..79ae71a661b 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Kart sahibinin adı" }, + "loginCredentials": { + "message": "Giriş məlumatları" + }, + "authenticatorKey": { + "message": "Kimlik doğrulayıcı açarı" + }, "number": { "message": "Nömrə" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Kimlik doğrulayıcı açarı (TOTP)" }, + "totpHelperTitle": { + "message": "2 addımlı doğrulama problemsiz edin" + }, + "totpHelper": { + "message": "Bitwarden, 2 addımlı doğrulama kodlarını saxlaya və doldura bilər. Açarı kopyalayıb bu xanaya yapışdırın." + }, + "totpHelperWithCapture": { + "message": "Bitwarden, 2 addımlı doğrulama kodlarını saxlaya və doldura bilər. Bu veb sayt kimlik doğrulayıcı QR kodunun ekran şəklini çəkmək üçün kamera ikonunu seçin, ya da açarı kopyalayıb bu xanaya yapışdırın." + }, "folder": { "message": "Qovluq" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Seansın müddəti bitdi." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Çıxış etmək istədiyinizə əminsiniz?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Heç nə seçmədiniz." }, - "receiveMarketingEmails": { - "message": "Elanlar, məsləhətlər və araşdırma fürsətləri üçün Bitwarden-dən e-poçt alın." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Abunəlikdən çıx" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Doğrulama kodu tələb olunur." }, + "webauthnCancelOrTimeout": { + "message": "Kimlik doğrulama ləğv edildi və ya çox uzun çəkdi. Lütfən yenidən sınayın." + }, "invalidVerificationCode": { "message": "Yararsız doğrulama kodu" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Duo xidmətinə bağlanarkən xəta baş verdi. Fərqli iki addımlı giriş üsulu istifadə edin və ya kömək üçün Duo ilə əlaqə saxlayın." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "DUO-nu başladın və giriş prosesini tamamlamaq üçün addımları izləyin." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "satın alınmış yerlər silindi" } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index ba53bcc52e4..8ee60ec12a2 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Імя ўладальніка карткі" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Нумар" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Ключ аўтэнтыфікацыі (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Папка" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Тэрмін дзеяння вашага сеансу завяршыўся." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Вы сапраўды хочаце выйсці?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Вы пакуль нічога не выбралі." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Патрабуецца праверачны код." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Памылковы праверачны код" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 9d7e581c3f7..a04ea2adc74 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Име на притежателя на картата" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Номер" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Удостоверителен ключ (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Папка" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Сесията ви изтече." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Сигурни ли сте, че искате да се отпишете?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Не сте избрали нищо." }, - "receiveMarketingEmails": { - "message": "Получавайте е-писма от Битоурден за новини, съвети и възможности за проучвания." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Отписване" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Кодът за потвърждение е задължителен." }, + "webauthnCancelOrTimeout": { + "message": "Удостоверяването беше отменено или отне твърде много време. Моля, опитайте отново." + }, "invalidVerificationCode": { "message": "Грешен код за потвърждаване" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Грешка при свързването с услугата на Duo. Използвайте друг метод за двустепенно удостоверяване или се свържете с Duo за съдействие." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Стартирайте DUO и следвайте инструкциите, за да завършите вписването." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 962b70c0839..8a0ea4f919b 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "কার্ডধারীর নাম" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "নম্বর" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "প্রমাণীকরণকারী কী (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "ফোল্ডার" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 198150c31de..803c5e1ec0d 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Ime vlasnika kartice" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Broj" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Ključ Autentifikatora (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Fascikla" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index bba01123c09..953e5c8c0da 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Nom del titular de la targeta" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Número" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Clau d'autenticació (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Carpeta" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "La vostra sessió ha caducat." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Segur que voleu tancar la sessió?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "No heu seleccionat res." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "El codi de verificació és obligatori." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Codi de verificació no vàlid" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Inicieu DUO i seguiu els passos per finalitzar la sessió." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 31efa53c824..f7b8bb6f28c 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Jméno držitele karty" }, + "loginCredentials": { + "message": "Přihlašovací údaje" + }, + "authenticatorKey": { + "message": "Ověřovací klíč" + }, "number": { "message": "Číslo" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Autentizační klíč (TOTP)" }, + "totpHelperTitle": { + "message": "Bezproblémové dvoufázové ověřování" + }, + "totpHelper": { + "message": "Bitwarden umí ukládat a vyplňovat dvoufázové ověřovací kódy. Zkopírujte a vložte klíč do tohoto pole." + }, + "totpHelperWithCapture": { + "message": "Bitwarden umí ukládat a vyplňovat dvoufázové ověřovací kódy. Vyberte ikonu fotoaparátu a pořiďte snímek obrazovky ověřovacího QR kódu této webové stránky nebo klíč zkopírujte a vložte do tohoto pole." + }, "folder": { "message": "Složka" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Platnost přihlášení vypršela." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Opravdu se chcete odhlásit?" }, @@ -855,7 +882,7 @@ "message": "E-mailová adresa" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Váš trezor je uzamčen" }, "uuid": { "message": "UUID" @@ -992,7 +1019,7 @@ "message": "Použije YubiKey pro přístup k Vašemu trezoru. Podporuje YubiKey 4, 5 a NEO." }, "duoDescV2": { - "message": "Zadejte kód vygenerovaný DUO Security.", + "message": "Zadejte kód vygenerovaný Duo Security.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1060,7 +1087,7 @@ "message": "Opravdu chcete pokračovat?" }, "moveSelectedItemsDesc": { - "message": "Choose a folder that you would like to add the $COUNT$ selected item(s) to.", + "message": "Vyberte složku, do které chcete přidat $COUNT$ vybraných položek.", "placeholders": { "count": { "content": "$1", @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Nevybrali jste žádné položky." }, - "receiveMarketingEmails": { - "message": "Získejte e-maily od Bitwardenu pro oznámení, poradenství a výzkumné příležitosti." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Odhlásit odběr" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Je vyžadován ověřovací kód." }, + "webauthnCancelOrTimeout": { + "message": "Ověření bylo zrušeno nebo trvalo příliš dlouho. Zkuste to znovu." + }, "invalidVerificationCode": { "message": "Neplatný ověřovací kód" }, @@ -6210,14 +6240,17 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Chyba při připojování ke službě Duo. Použijte jinou dvoufázovou metodu přihlášení nebo kontaktujte Duo o pomoc." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Spusťte DUO a pro dokončení přihlášení postupujte podle kroků." + "message": "Spusťte Duo a pro dokončení přihlášení postupujte podle kroků." }, "duoRequiredByOrgForAccount": { - "message": "Pro Váš účet je vyžadováno dvoufázové přihlášení DUO." + "message": "Pro Váš účet je vyžadováno dvoufázové přihlášení Duo." }, "launchDuo": { - "message": "Spustit DUO" + "message": "Spustit Duo" }, "turnOn": { "message": "Zapnout" @@ -7883,7 +7916,7 @@ "message": "Přiřadit k těmto kolekcím" }, "bulkCollectionAssignmentDialogDescription": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "Jen členové organizace s přístupem k těmto kolekcím budou moci vidět položky." }, "selectCollectionsToAssign": { "message": "Vyberte kolekce pro přiřazení" @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "zakoupení uživatelé odebráni" } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 6ebf3c97f8f..ddf33bd7225 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Enw ar y cerdyn" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Rhif" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Ffolder" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 1d64000aad4..ce90429020b 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Kortholdernavn" }, + "loginCredentials": { + "message": "Login-legitimationsoplysninger" + }, + "authenticatorKey": { + "message": "Godkendelsesnøgle" + }, "number": { "message": "Nummer" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Godkendelsesnøgle (TOTP)" }, + "totpHelperTitle": { + "message": "Gør 2-trinsbekræftelse problemfri" + }, + "totpHelper": { + "message": "Bitwarden kan gemme og udfylde 2-trinsbekræftelseskoder. Kopiér og indsæt nøglen i dette felt." + }, + "totpHelperWithCapture": { + "message": "Bitwarden kan gemme og udfylde 2-trinsbekræftelseskoder. Vælg kameraikonet for at tage et skærmfoto af dette websteds godkendelses-QR-kode, eller kopiér og indsæt nøglen i dette felt." + }, "folder": { "message": "Mappe" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Login-session udløbet." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Sikker på, at du vil logge ud?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Du har ikke valgt noget." }, - "receiveMarketingEmails": { - "message": "Få e-mails fra Bitwarden om annonceringer, råd og forskningsmuligheder." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Afmeld" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Bekræftelseskode kræves." }, + "webauthnCancelOrTimeout": { + "message": "Godkendelsen blev afbrudt eller tog for lang tid. Forsøg igen." + }, "invalidVerificationCode": { "message": "Ugyldig bekræftelseskode" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Fejl under forbindelsesoprettelsen til Duo-tjenesten. Brug en anden totrins-indlogningsmetode eller kontakt Duo for hjælp." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Start DUO og følg trinene for at fuldføre indlogningen." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "købte pladser fjernet" } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 808956a4db3..fbf41456746 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Name des Karteninhabers" }, + "loginCredentials": { + "message": "Zugangsdaten" + }, + "authenticatorKey": { + "message": "Authenticator-Schlüssel" + }, "number": { "message": "Nummer" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Authentifizierungsschlüssel (TOTP)" }, + "totpHelperTitle": { + "message": "Zwei-Faktor-Authentifizierung nahtlos gestalten" + }, + "totpHelper": { + "message": "Bitwarden kann Zwei-Faktor-Authentifizierungsscodes speichern und ausfüllen. Kopiere und füge den Schlüssel in dieses Feld ein." + }, + "totpHelperWithCapture": { + "message": "Bitwarden kann Zwei-Faktor-Authentifizierungsscodes speichern und ausfüllen. Wähle das Kamerasymbol aus, um einen Screenshot des Authenticator-QR-Codes dieser Website zu machen oder kopiere und füge den Schlüssel in dieses Feld ein." + }, "folder": { "message": "Ordner" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Ihre Anmeldungsitzung ist abgelaufen." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Bist du sicher, dass du dich abmelden möchtest?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Sie haben keine Auswahl getroffen." }, - "receiveMarketingEmails": { - "message": "Erhalte E-Mails von Bitwarden für Ankündigungen, Ratschläge und Forschungsmöglichkeiten." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Deabonnieren" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verifizierungscode ist erforderlich." }, + "webauthnCancelOrTimeout": { + "message": "Die Authentifizierung wurde abgebrochen oder hat zu lange gedauert. Bitte versuche es erneut." + }, "invalidVerificationCode": { "message": "Ungültiger Verifizierungscode" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Fehler beim Verbinden mit dem Duo-Dienst. Verwende eine andere Zwei-Faktor-Authentifizierungsmethode oder kontaktiere Duo für Hilfe." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Starte DUO und folge den Schritten, um die Anmeldung abzuschließen." }, @@ -7883,7 +7916,7 @@ "message": "Diesen Sammlungen zuweisen" }, "bulkCollectionAssignmentDialogDescription": { - "message": "Wähle die Sammlungen, mit denen die Einträge geteilt werden. Sobald ein Eintrag in einer Sammlung aktualisiert wird, wird es in allen Sammlungen reflektiert. Nur Mitglieder von Organisationen mit Zugang zu diesen Sammlungen können die Einträge sehen." + "message": "Nur Organisationsmitglieder mit Zugriff auf diese Sammlungen können die Einträge sehen." }, "selectCollectionsToAssign": { "message": "Zu zuweisende Sammlungen auswählen" @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "erworbene Benutzerplätze entfernt" } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 6ce73ff9532..2d0a21d05e0 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Όνομα κατόχου της κάρτας" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Αριθμός" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Κλειδί επαλήθευσης (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Φάκελος" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Η περίοδος σύνδεσής σας έχει λήξει." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Είστε βέβαιοι ότι θέλετε να αποσυνδεθείτε;" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Δεν έχετε επιλέξει τίποτα." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Απαιτείται ο κωδικός επαλήθευσης." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Μη έγκυρος κωδικός επαλήθευσης" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index ba4e6b7174a..6c16d8569fa 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -570,6 +570,24 @@ } } }, + "itemsMovedToOrg": { + "message": "Items moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, + "itemMovedToOrg": { + "message": "Item moved to $ORGNAME$", + "placeholders": { + "orgname": { + "content": "$1", + "example": "Company Name" + } + } + }, "deleteItem": { "message": "Delete item" }, @@ -627,6 +645,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -1476,8 +1506,8 @@ "importEncKeyError": { "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." }, - "importDestination": { - "message": "Import destination" + "destination": { + "message": "Destination" }, "learnAboutImportOptions": { "message": "Learn about your import options" @@ -3798,8 +3828,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -7903,7 +7933,10 @@ "assignToTheseCollections": { "message": "Assign to these collections" }, - "bulkCollectionAssignmentDialogDescription": { + "bulkCollectionAssignmentDialogDescriptionSingular": { + "message": "Only organization members with access to these collections will be able to see the item." + }, + "bulkCollectionAssignmentDialogDescriptionPlural": { "message": "Only organization members with access to these collections will be able to see the items." }, "selectCollectionsToAssign": { @@ -8558,21 +8591,33 @@ "selectFolder": { "message": "Select folder" }, - "personalItemsTransferWarning": { - "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "personalItemTransferWarningSingular": { + "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + }, + "personalItemsTransferWarningPlural": { + "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", "placeholders": { "personal_items_count": { "content": "$1", - "example": "2 items" + "example": "2" } } }, - "personalItemsWithOrgTransferWarning": { - "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "personalItemWithOrgTransferWarningSingular": { + "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "placeholders": { + "org": { + "content": "$1", + "example": "Organization name" + } + } + }, + "personalItemsWithOrgTransferWarningPlural": { + "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", "placeholders": { "personal_items_count": { "content": "$1", - "example": "2 items" + "example": "2" }, "org": { "content": "$2", @@ -8580,6 +8625,9 @@ } } }, + "data": { + "message": "Data" + }, "purchasedSeatsRemoved": { "message": "purchased seats removed" } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index fd5f3b28293..5a36ac27915 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Cardholder name" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Number" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Folder" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index e21f70f68ee..6128c93fb36 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Cardholder name" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Number" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Folder" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 5150b57946d..9d509e09cc6 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Nomo de la posedanto de la karto" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Numero" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Ŝlosilo de aŭtentigo (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Dosierujo" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Via seanco eksvalidiĝis." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Ĉu vi certas ke vi volas adiaŭi?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Vi elektis nenion." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Aŭtentiga kodo estas deviga." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 97a0dc44f73..195d184eac3 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Nombre en la tarjeta" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Número" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Clave de autenticación (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Carpeta" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Tu sesión ha expirado." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "¿Estás seguro de querer cerrar la sesión?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "No has seleccionado nada." }, - "receiveMarketingEmails": { - "message": "Obtén correos electrónicos de Bitwarden para anuncios, consejos y oportunidades de investigación." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Darse de baja" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "El código de verificación es requerido." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Código de verificación no válido" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -7661,7 +7694,7 @@ "message": "Limit collection creation and deletion to owners and admins" }, "allowAdminAccessToAllCollectionItemsDesc": { - "message": "Owners and admins can manage all collections and items" + "message": "Propietarios y administradores pueden gestionar todas las colecciones y elementos" }, "updatedCollectionManagement": { "message": "Updated collection management setting" @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index d166e1747e7..5a336649014 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Kaardiomaniku nimi" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Kaardi number" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Autentimise võti (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Kaust" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Sessioon on aegunud." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Oled kindel, et soovid välja logida?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Midagi pole valitud." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Lõpeta tellimus" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Kinnituskood on nõutav." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Vale kinnituskood" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 1220a5dc637..5a6e143fd78 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Txartelaren titularraren izena" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Zenbakia" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Autentifikazio-gakoa (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Karpeta" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Saioa amaitu da." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Ziur zaude saioa itxi nahi duzula?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Ez duzu ezer aukeratu." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Egiaztatze-kodea behar da." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Egiaztatze-kodea ez da baliozkoa" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 3156ac2b889..d2466d511bc 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "نام صاحب کارت" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "شماره" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "کلید احراز هویت (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "پوشه" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "نشست ورود شما منقضی شده است." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "آیا مطمئنید که می‌خواهید خارج شوید؟" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "شما چیزی را انتخاب نکرده اید." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "کد تأیید مورد نیاز است." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index eb7fcb5ba0b..f70a820c6a4 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Kortinhaltijan nimi" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Numero" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Todennusavain (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Kansio" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Kirjautumisistuntosi on erääntynyt." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Haluatko varmasti kirjautua ulos?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Et ole valinnut mitään." }, - "receiveMarketingEmails": { - "message": "Vastaanota Bitwardenilta uutiskirjeitä julkaisuista, tukiresursseista ja tutkimusmahdollisuuksista." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Lopeta tilaus" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Todennuskoodi vaaditaan." }, + "webauthnCancelOrTimeout": { + "message": "Todennus peruutettiin tai siinä kesti liian kauan. Yritä uudelleen." + }, "invalidVerificationCode": { "message": "Virheellinen todennuskoodi" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Virhe yhdistettäessä Duo-palveluun. Käytä toista kaksivaiheista kirjautumismenetelmää tai ota yhteyttä Duoon saadaksesi apua." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Avaa Duo ja viimeistele kirjautuminen seuraamalla ohjeita." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index a89ecc6d2e3..a5fa31a531c 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Pangalan ng cardholder" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Numero" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Folder" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Nag-expire na ang login session mo." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Sigurado ka bang gusto mong mag-log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Wala kang pinili." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Kinakailangan ang verification code." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Maling verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index ee1c81c74b6..a10d4e41717 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Nom du titulaire de la carte" }, + "loginCredentials": { + "message": "Identifiants de connexion" + }, + "authenticatorKey": { + "message": "Clé d'authentification" + }, "number": { "message": "Numéro" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Clé d'authentification (TOTP)" }, + "totpHelperTitle": { + "message": "Rendre la vérification en deux étapes transparente" + }, + "totpHelper": { + "message": "Bitwarden peut stocker et remplir des codes de vérification en 2 étapes. Copiez et collez la clé dans ce champ." + }, + "totpHelperWithCapture": { + "message": "Bitwarden peut stocker et remplir des codes de vérification en 2 étapes. Sélectionnez l'icône caméra pour prendre une capture d'écran du code QR de l'authentificateur de ce site Web, ou copiez et collez la clé dans ce champ." + }, "folder": { "message": "Dossier" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Votre session a expiré." }, + "restartRegistration": { + "message": "Redémarrer l'inscription" + }, + "expiredLink": { + "message": "Lien expiré" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Veuillez redémarrer votre inscription ou essayez de vous connecter." + }, + "youMayAlreadyHaveAnAccount": { + "message": "Vous avez peut-être déjà un compte" + }, "logOutConfirmation": { "message": "Êtes-vous sûr de vouloir vous déconnecter ?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Vous n'avez rien sélectionné." }, - "receiveMarketingEmails": { - "message": "Recevez des courriels de Bitwarden pour des annonces, des conseils et des opportunités de recherche." + "receiveMarketingEmailsV2": { + "message": "Obtenez des conseils, des annonces et des opportunités de recherche de la part de Bitwarden dans votre boîte de réception." }, "unsubscribe": { "message": "Désabonnez-vous" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Le code de vérification est requis." }, + "webauthnCancelOrTimeout": { + "message": "L'authentification a été annulée ou a pris trop de temps. Veuillez réessayer." + }, "invalidVerificationCode": { "message": "Code de vérification invalide" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Erreur de connexion avec le service Duo. Utilisez une autre méthode de connexion en deux étapes ou contactez Duo pour obtenir de l'aide." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Lancez DUO et suivez les étapes pour terminer la connexion." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "licenses achetées retirées" } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index a0393577921..31a61b1185e 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Cardholder name" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Number" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Folder" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index a5ae8083f2b..8fa80d92984 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "שם בעל הכרטיס" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "מספר" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "מפתח מאמת (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "תיקייה" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "תוקף החיבור שלך הסתיים." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "האם אתה בטוח שברצונך להתנתק?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "לא בחרת כלום." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index fa4e16d6ea6..113d1a29407 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "कार्डधारक का नाम" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "संख्या" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "फ़ोल्डर" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 8e9ca538308..e681c0d6c5f 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Vlasnik platne kartice" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Broj" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Ključ autentifikatora (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Mapa" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Sesija je istekla." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Sigurno se želiš odjaviti?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Ništa nije odabrano." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Potvrdni kôd je obavezan." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Nevažeći kôd za provjeru" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 02f64df294c..523192f3fff 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Kártyatulajdonos neve" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Szám" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Hitelesítő kulcs (egyszeri idő alapú)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Mappa" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "A bejelentkezési munkamenet lejárt." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Biztosan szeretnénk kijelentkezni?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Nincs kiválasztva semmi." }, - "receiveMarketingEmails": { - "message": "Emaileket kaphatunk a Bitwardentől bejelentésekről, tanácsokról és kutatási lehetőségekről." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Leiratkozás" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Az ellenőrző kód kötelező." }, + "webauthnCancelOrTimeout": { + "message": "A hitelesítés megszakításra került vagy túl sokáig tartott. Próbáljuk újra." + }, "invalidVerificationCode": { "message": "Érvénytelen ellenőrző kód" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Hiba történt a Duo szolgáltatáshoz csatlakozáskor. Használjunk másik kétlépcsős bejelentkezési módot vagy kérjünk segítséget a Duotól." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Indítsuk el a DUO-t és kövessük a lépéseket a bejelentkezés befejezéséhez." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "megvásárolt hely eltávolításra került." } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index c6370a50ab2..8d4babe7348 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Nama Pemegang Kartu" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Nomor" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Kunci Autentikasi (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Direktori" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Sesi masuk Anda telah berakhir." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Anda yakin ingin keluar?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Anda belum memilih apa pun." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Kode verifikasi diperlukan." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Kode verifikasi tidak valid" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 34d9512210e..fc3fcd0f06d 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Titolare della carta" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Numero" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Chiave di autenticazione (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Cartella" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "La tua sessione è scaduta." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Sei sicuro di voler uscire?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Non hai selezionato nulla." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Codice di verifica obbligatorio." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Codice di verifica non valido" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Avvia DUO e segui i passaggi per finire di accedere." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index e8ba94d82bd..1eb81bb6a02 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "カードの名義人名" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "番号" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "認証キー (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "フォルダー" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "ログインセッションの有効期限が切れています。" }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "ログアウトしてもよろしいですか?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "何も選択されていません。" }, - "receiveMarketingEmails": { - "message": "Bitwarden からのお知らせ、アドバイス、アンケート調査等のメールを受信します。" + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "配信停止" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "認証コードが必要です。" }, + "webauthnCancelOrTimeout": { + "message": "認証がキャンセルされたか、時間がかかりすぎました。もう一度やり直してください。" + }, "invalidVerificationCode": { "message": "認証コードが間違っています" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Duo サービスへの接続中にエラーが発生しました。異なる二段階ログイン方法を使用するか、Duo に連絡してください。" + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "ログインを完了するには DUO を起動し手順に従ってください。" }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "購入済みシートが削除されました" } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index cb76f9ad569..d34d19e693f 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "ბარათის მფლობელის სახელი" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "ნომერი" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "ავთენტიკატორის გასაღები (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "საქაღალდე" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "თქვენი სისტემაში შესვლის სესიას ვადა გაუვიდა." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "ნამდვილად გსურთ გასვლა სისტემიდან?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index a0393577921..31a61b1185e 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Cardholder name" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Number" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Folder" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 2b9b8a3806e..5e04b927407 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "ಕಾರ್ಡುದಾರನ ಹೆಸರು" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "ಸಂಖ್ಯೆ" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "ದೃಢೀಕರಣ ಕೀ (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "ಫೋಲ್ಡರ್" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "ನಿಮ್ಮ ಲಾಗಿನ್ ಸೆಷನ್ ಅವಧಿ ಮೀರಿದೆ." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "ಲಾಗ್ ಔಟ್ ಮಾಡಲು ನೀವು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "ನೀವು ಯಾವುದನ್ನೂ ಆಯ್ಕೆ ಮಾಡಿಲ್ಲ." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 0fb2f23a524..e92a2a4ecf4 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "카드 소유자 이름" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "번호" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "인증 키 (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "폴더" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "로그인 세션이 만료되었습니다." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "정말 로그아웃하시겠습니까?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "아무것도 선택하지 않았습니다." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "인증 코드는 반드시 입력해야 합니다." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "유효하지 않은 확인 코드" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index e69ebf72e9c..8a28a838240 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Kartes īpašnieka vārds" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Numurs" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Autentificētāja atslēga (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Mape" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Pieteikšanās sesija ir beigusies." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Vai tiešām atteikties?" }, @@ -1060,7 +1087,7 @@ "message": "Vai tiešām vēlaties turpināt?" }, "moveSelectedItemsDesc": { - "message": "Choose a folder that you would like to add the $COUNT$ selected item(s) to.", + "message": "Jāizvelas mape, kurā pievienot $COUNT$ atlasīto(s) vienumu(s).", "placeholders": { "count": { "content": "$1", @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Nekas nav atlasīts." }, - "receiveMarketingEmails": { - "message": "Saņemt e-pasta ziņojumus no Bitwarden par paziņojumiem, padomiem un izpētes iespējām." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Atteikt abonēšanu" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Ir nepieciešams apstiprinājuma kods." }, + "webauthnCancelOrTimeout": { + "message": "Autentifikācija tika atcelta vai tā aizņēma pārāk daudz laika. Lūgums mēģināt vēlreiz." + }, "invalidVerificationCode": { "message": "Nederīgs apstiprinājuma kods" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Kļūda savienojuma izveidošanā ar Duo pakalpojumu. Jāizmanto cits divpakāpju pieteikšanāš veids vai jāvēršas pie Duo pēc palīdzības." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Jāpalaiž DUO un jāseko soļiem, lai pabeigtu pieteikšanos." }, @@ -7570,7 +7603,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsNoItemsMessage": { - "message": "Send ir izmantojami, lai ar ikvienu droši kopīgotu šifrētu informāciju.", + "message": "Send ir izmantojams, lai ar ikvienu droši kopīgotu šifrētu informāciju.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inviteUsers": { @@ -7883,7 +7916,7 @@ "message": "Piešķirt šiem krājumiem" }, "bulkCollectionAssignmentDialogDescription": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "Vienumus varēs redzēt tikai apvnienības dalībnieki ar piekļuvi šiem krājumiem." }, "selectCollectionsToAssign": { "message": "Atlasīt krājumus, lai piešķirtu" @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "iegādātās vietas noņemtas" } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index ddab9ea2a9f..137a78e0e2e 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "കാർഡ് ഉടമയുടെ പേര്" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "നമ്പർ" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "ഓതന്റിക്കേറ്റർ കീ (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "ഫോൾഡർ" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "നിങ്ങളുടെ പ്രവർത്തന സമയം കഴിഞ്ഞിരിക്കുന്നു." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "നിങ്ങൾക്ക് ലോഗ് ഔട്ട് ചെയ്യണമെന്ന് ഉറപ്പാണോ?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "നിങ്ങൾ ഒന്നും തിരഞ്ഞെടുത്തിട്ടില്ല." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index a0393577921..31a61b1185e 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Cardholder name" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Number" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Folder" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index a0393577921..31a61b1185e 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Cardholder name" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Number" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Folder" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 68c70f67682..31e8c793d56 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Kortholderens navn" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Nummer" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Autentiseringsnøkkel (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Mappe" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Din innloggingsøkt har utløpt." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Er du sikker på at du vil logge av?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Du har ikke valgt noe." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "En verifiseringskode er påkrevd." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Ugyldig bekreftelseskode" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 333d5a6f87f..79f2ed80be6 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Cardholder name" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "नम्बर" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "प्रमाणक कुञ्जी (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "फोल्‍डर" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index d24ce9e5a3b..5ac8df11bf0 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Naam kaarthouder" }, + "loginCredentials": { + "message": "Inloggegevens" + }, + "authenticatorKey": { + "message": "Authenticatiesleutel" + }, "number": { "message": "Kaartnummer" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Authenticatiesleutel (TOTP)" }, + "totpHelperTitle": { + "message": "Maak tweestapsaanmelding naadloos" + }, + "totpHelper": { + "message": "Bitwarden kan tweestapsaanmeldingscodes opslaan en invullen. Kopieer en plak de sleutel in dit veld." + }, + "totpHelperWithCapture": { + "message": "Bitwarden kan tweestapsaanmeldingscodes opslaan en invullen. Selecteer het camerapictogram om een schermafbeelding van de QR-code van deze website te maken of kopieer en plak de sleutel in dit veld." + }, "folder": { "message": "Map" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Je inlogsessie is verlopen." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Weet je zeker dat je wilt uitloggen?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Je hebt niets geselecteerd." }, - "receiveMarketingEmails": { - "message": "Ontvang e-mailberichten van Bitwarden voor aankondigingen, advies en onderzoeksmogelijkheden." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Afmelden" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verificatiecode vereist." }, + "webauthnCancelOrTimeout": { + "message": "De authenticatie werd geannuleerd of duurde te lang. Probeer het opnieuw." + }, "invalidVerificationCode": { "message": "Ongeldige verificatiecode" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Fout bij het verbinden met de Duo-service. Gebruik een andere tweestapsaanmeldingsmethode of neem contact op met Duo voor hulp." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Start DUO en volg de stappen om in te loggen." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "gekochte plaatsen verwijderd" } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 7ac77cec177..0aeb90abb74 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Namn til korteigar" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Nummer" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Autentiseringsnøkkel (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Mappe" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index a0393577921..31a61b1185e 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Cardholder name" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Number" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Folder" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index f5a39ccce9f..5cd49c6e2ad 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Właściciel karty" }, + "loginCredentials": { + "message": "Dane logowania" + }, + "authenticatorKey": { + "message": "Klucz uwierzytelniający" + }, "number": { "message": "Numer" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Klucz uwierzytelniający (TOTP)" }, + "totpHelperTitle": { + "message": "Spraw, aby dwuetapowa weryfikacja była bezproblemowa" + }, + "totpHelper": { + "message": "Bitwarden może przechowywać i wypełniać kody weryfikacyjne. Skopiuj i wklej klucz do tego pola." + }, + "totpHelperWithCapture": { + "message": "Bitwarden może przechowywać i wypełniać kody weryfikacyjne. Wybierz ikonę aparatu, aby zrobić zrzut ekranu z kodem QR lub skopiuj i wklej klucz do tego pola." + }, "folder": { "message": "Folder" }, @@ -601,7 +616,7 @@ "message": "Poziom dostępu" }, "accessing": { - "message": "Accessing" + "message": "Uzyskiwanie dostępu" }, "loggedOut": { "message": "Wylogowano" @@ -612,6 +627,18 @@ "loginExpired": { "message": "Twoja sesja wygasła." }, + "restartRegistration": { + "message": "Zrestartuj rejestrację" + }, + "expiredLink": { + "message": "Link wygasł" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Zrestartuj rejestrację lub spróbuj się zalogować." + }, + "youMayAlreadyHaveAnAccount": { + "message": "Możesz mieć już konto" + }, "logOutConfirmation": { "message": "Czy na pewno chcesz się wylogować?" }, @@ -1060,7 +1087,7 @@ "message": "Czy na pewno chcesz kontynuować?" }, "moveSelectedItemsDesc": { - "message": "Choose a folder that you would like to add the $COUNT$ selected item(s) to.", + "message": "Wybierz folder, do którego chcesz dodać $COUNT$ wybrane(ych) elementy(ów).", "placeholders": { "count": { "content": "$1", @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Nie zaznaczyłeś żadnych elementów." }, - "receiveMarketingEmails": { - "message": "Otrzymuj e-maile od Bitwarden z ogłoszeniami, poradami i badaniami." + "receiveMarketingEmailsV2": { + "message": "Uzyskaj poradę, ogłoszenia i możliwości badawcze od Bitwarden w swojej skrzynce odbiorczej." }, "unsubscribe": { "message": "Anuluj subskrypcję" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Kod weryfikacyjny jest wymagany." }, + "webauthnCancelOrTimeout": { + "message": "Uwierzytelnianie zostało anulowane lub trwało zbyt długo. Spróbuj ponownie." + }, "invalidVerificationCode": { "message": "Kod weryfikacyjny jest nieprawidłowy" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Wystąpił błąd podczas połączenia z usługą Duo. Aby uzyskać pomoc, użyj innej metody dwustopniowego logowania lub skontaktuj się z Duo." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Uruchom DUO i wykonaj kroki, aby zakończyć logowanie." }, @@ -7883,7 +7916,7 @@ "message": "Przypisz do tych kolekcji" }, "bulkCollectionAssignmentDialogDescription": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "Tylko członkowie organizacji z dostępem do tych kolekcji będą mogli zobaczyć te elementy." }, "selectCollectionsToAssign": { "message": "Wybierz kolekcje do przypisania" @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "zakupione miejsca usunięte" } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index ea8ea2db5f4..f07cc298931 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Nome do titular do cartão" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Número" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Chave de Autenticação (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Pasta" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "A sua sessão expirou." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Você tem certeza que deseja sair?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Você selecionou nada." }, - "receiveMarketingEmails": { - "message": "Obtenha e-mails do Bitwarden para anúncios, conselhos e oportunidades de pesquisa." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Cancelar subscrição" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "O código de verificação é necessário." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Código de verificação inválido" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Inicie o Duo e siga os passos para finalizar o login." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index af5f1fb4000..9c6b41fbb81 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Titular do cartão" }, + "loginCredentials": { + "message": "Credenciais de início de sessão" + }, + "authenticatorKey": { + "message": "Chave de autenticação" + }, "number": { "message": "Número" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Chave de autenticação (TOTP)" }, + "totpHelperTitle": { + "message": "Torne a verificação de dois passos simples" + }, + "totpHelper": { + "message": "O Bitwarden pode armazenar e preencher códigos de verificação de dois passos. Copie e cole a chave neste campo." + }, + "totpHelperWithCapture": { + "message": "O Bitwarden pode armazenar e preencher códigos de verificação de dois passos. Selecione o ícone da câmara para tirar uma captura de ecrã do código QR do autenticador deste site ou copie e cole a chave neste campo." + }, "folder": { "message": "Pasta" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "A sua sessão expirou." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Tem a certeza de que pretende terminar sessão?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Não selecionou nada." }, - "receiveMarketingEmails": { - "message": "Receba e-mails do Bitwarden com anúncios, conselhos e oportunidades de investigação." + "receiveMarketingEmailsV2": { + "message": "Receba conselhos, anúncios e oportunidades de investigação do Bitwarden na sua caixa de entrada." }, "unsubscribe": { "message": "Anular subscrição" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "É necessário o código de verificação." }, + "webauthnCancelOrTimeout": { + "message": "A autenticação foi cancelada ou demorou demasiado tempo. Por favor, tente novamente." + }, "invalidVerificationCode": { "message": "Código de verificação inválido" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Erro ao ligar ao serviço Duo. Utilize um método de verificação de dois passos diferente ou contacte o Duo para obter assistência." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Inicie o DUO e siga os passos para concluir o início de sessão." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "lugares adquiridos removidos" } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 421b3ac2720..aa8a0aa6178 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Numele titularului cardului" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Număr card" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Cheie de autentificare (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Dosar" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Sesiunea de autentificare a expirat." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Sigur doriți să vă deconectați?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Nu ați selectat nimic." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Este necesar codul de verificare." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Cod de verificare nevalid" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 33f943c3d0a..a370b92d980 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Имя владельца карты" }, + "loginCredentials": { + "message": "Данные для авторизации" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Номер" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Ключ аутентификатора (TOTP)" }, + "totpHelperTitle": { + "message": "Сделать двухэтапную аутентификацию бесшовной" + }, + "totpHelper": { + "message": "Bitwarden может хранить и заполнять коды двухэтапной аутентификации. Скопируйте и вставьте ключ в это поле." + }, + "totpHelperWithCapture": { + "message": "Bitwarden может хранить и заполнять коды двухэтапной аутентификации. Выберите значок камеры, чтобы сделать скриншот QR-кода этого сайта, или скопируйте и вставьте ключ в это поле." + }, "folder": { "message": "Папка" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Истек срок действия вашей сессии." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Вы действительно хотите выйти?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Вы ничего не выбрали." }, - "receiveMarketingEmails": { - "message": "Получайте электронные письма от Bitwarden с анонсами, советами и возможностями для исследований." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Отписаться" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Необходимо ввести код подтверждения." }, + "webauthnCancelOrTimeout": { + "message": "Аутентификация была отменена или заняла слишком много времени. Пожалуйста, попробуйте еще раз." + }, "invalidVerificationCode": { "message": "Неверный код подтверждения" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Ошибка при подключении к сервису Duo. Используйте другой метод двухэтапной аутентификации или обратитесь за помощью в Duo." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Запустите Duo и следуйте шагам для завершения авторизации." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "купленные места удалены" } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index c750bb57545..c4adb0521e5 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Cardholder name" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "අංකය" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "බහාලුම" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 933014f8726..78750ade85d 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Meno vlastníka karty" }, + "loginCredentials": { + "message": "Prihlasovacie údaje" + }, + "authenticatorKey": { + "message": "Kľúč overovacej aplikácie" + }, "number": { "message": "Číslo" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Kľúč overovateľa (TOTP)" }, + "totpHelperTitle": { + "message": "Spravte dvojstupňové overenie bezproblémovým" + }, + "totpHelper": { + "message": "Bitwarden umožňuje uložiť a vyplniť kódy dvojstupňového overenia. Skopírujte a vložte kľúč do tohto poľa." + }, + "totpHelperWithCapture": { + "message": "Bitwarden umožňuje uložiť a vyplniť kódy dvojstupňového overenia. Vyberte ikonu fotoaparátu a zosnímajte obrazovku QR kódu overovacej aplikácie tejto webovej stránky alebo skopírujte a vložte kľúč do tohto poľa." + }, "folder": { "message": "Priečinok" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Platnosť prihlásenia vypršala." }, + "restartRegistration": { + "message": "Zopakovať registráciu" + }, + "expiredLink": { + "message": "Odkaz expiroval" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Prosím zopakujte registráciu alebo sa pokúste prihlásiť." + }, + "youMayAlreadyHaveAnAccount": { + "message": "Možno už máte účet" + }, "logOutConfirmation": { "message": "Naozaj sa chcete odhlásiť?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Nič ste nevybrali." }, - "receiveMarketingEmails": { - "message": "Dostávať e-maily od Bitwardenu s oznámeniami, radami a možnosťami výskumu." + "receiveMarketingEmailsV2": { + "message": "Dostávajte do schránky rady, oznámenia a príležitosti na výskum od spoločnosti Bitwarden." }, "unsubscribe": { "message": "Odhlásiť sa z odberu" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Overovací kód je povinný." }, + "webauthnCancelOrTimeout": { + "message": "Overenie bolo zrušené alebo trvalo príliš dlho. Skúste to znova." + }, "invalidVerificationCode": { "message": "Neplatný verifikačný kód" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Chyba pri pripájaní k službe Duo. Použite inú metódu dvojstupňového prihlásenia alebo kontaktujte Duo a požiadajte o pomoc." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Spustite DUO a postupujte podľa pokynov na dokončenie prihlásenia." }, @@ -8532,13 +8565,13 @@ "message": "Po vykonaní aktualizácií na cloudovom serveri Bitwarden nahrajte svoj licenčný súbor, aby ste aplikovali najnovšie zmeny." }, "addToFolder": { - "message": "Add to folder" + "message": "Pridať do priečinka" }, "selectFolder": { - "message": "Select folder" + "message": "Vybrať zložku" }, "personalItemsTransferWarning": { - "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to the selected organization. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ sa natrvalo prenesie do vybranej organizácie. Tieto položky už nebudete vlastniť.", "placeholders": { "personal_items_count": { "content": "$1", @@ -8547,7 +8580,7 @@ } }, "personalItemsWithOrgTransferWarning": { - "message": "$PERSONAL_ITEMS_COUNT$ will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ sa natrvalo prenesie do $ORG$. Tieto položky už nebudete vlastniť.", "placeholders": { "personal_items_count": { "content": "$1", @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "zakúpené sedenia boli odstránené" } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index ed777b1251d..f476bb5b1a3 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Ime imetnika kartice" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Številka" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Ključ avtentikatorja (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Mapa" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Vaša seja je potekla." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Ste prepričani, da se želite odjaviti?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index c0e487db8e0..038e9436f72 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Име власника картице" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Број" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Једнократни код" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Фасцикла" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Ваша сесија је истекла." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Заиста желите да се одјавите?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Нисте ништа изабрали." }, - "receiveMarketingEmails": { - "message": "Добијајте е-пошту од Bitwarden-а за најаве, савете и могућности истраживања." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Одјави се" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Верификациони код је обавезан." }, + "webauthnCancelOrTimeout": { + "message": "Аутентификација је отказана или је трајала предуго. Молим вас, покушајте поново." + }, "invalidVerificationCode": { "message": "Неисправан верификациони код" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Грешка при повезивању са услугом Duo. Користите други метод пријаве у два корака или контактирајте Duo за помоћ." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Покренути DUO и пратите кораке да бисте завршили пријављивање." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "купљена места уклоњена" } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 574f1c9a3dd..02269bb0340 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Ime vlasnika kartice" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Broj" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Autentifikacioni Ključ (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Fascikla" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Vaša sesija je istekla." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Da li ste sigurni da želite da se odjavite?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 206bd766dc7..dceee53c585 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Kortinnehavarens namn" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Nummer" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Autentiseringsnyckel (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Mapp" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Din inloggningssession har upphört." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Är du säker på att du vill logga ut?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Du har inte markerat något." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Ogiltig verifieringskod" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index a0393577921..31a61b1185e 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Cardholder name" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Number" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Folder" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Your login session has expired." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Are you sure you want to log out?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index e2a456a04ef..f73ca20ee85 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "ชื่อผู้ถือบัตร" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "หมายเลข" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "คีย์ Authenticator (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "โฟลเดอร์" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "เซสชันของคุณหมดอายุแล้ว" }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "คุณต้องการล็อกเอาต์ใช่หรือไม่?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 27bd265dd99..dfa5464d6bb 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Kart sahibinin adı" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Numara" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Kimlik doğrulama anahtarı (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Klasör" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Oturumunuz zaman aşımına uğradı." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Çıkmak istediğinizden emin misiniz?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Hiçbir şey seçmediniz." }, - "receiveMarketingEmails": { - "message": "Bitwarden'dan duyurular, öneriler ve araştırmalarla ilgili e-postalar alın." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "İstediğiniz zaman" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Doğrulama kodu gereklidir." }, + "webauthnCancelOrTimeout": { + "message": "Kimlik doğrulama iptal edildi ve çok uzun sürdü. Lütfen yeniden deneyin." + }, "invalidVerificationCode": { "message": "Geçersiz doğrulama kodu" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Duo'yu başlatın ve oturum açmayı tamamlamak için adımları izleyin." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index e316cc0322d..cd91889b200 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Ім'я власника картки" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Номер" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Ключ автентифікації (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Тека" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Тривалість вашого сеансу завершилась." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Ви дійсно хочете вийти?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "Ви нічого не обрали." }, - "receiveMarketingEmails": { - "message": "Отримуйте електронні листи від Bitwarden з оголошеннями, порадами та інформацією про нові можливості." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Відписатися" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Потрібний код підтвердження." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Недійсний код підтвердження" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Запустіть Duo і виконайте дії для завершення входу." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 7f61058de48..a2b11db55b4 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "Tên chủ thẻ" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "Số" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "Khóa xác thực (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "Thư mục" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "Phiên đăng nhập của bạn đã hết hạn." }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "Bạn có chắc muốn đăng xuất?" }, @@ -1167,7 +1194,7 @@ "message": "Tài khoản bị hạn chế" }, "passwordProtected": { - "message": "Đã được bảo vệ mật khẩu" + "message": "Mật khẩu đã được bảo vệ" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "“Mật khẩu tập tin” và “Nhập lại mật khẩu tập tin” không khớp." @@ -1179,7 +1206,7 @@ "message": "Tập tin này được bảo vệ bằng mật khẩu. Vui lòng nhập mật khẩu để nhập dữ liệu." }, "exportSuccess": { - "message": "Đã xuất dữ liệu kho cảu bạn" + "message": "Đã xuất dữ liệu kho của bạn" }, "passwordGenerator": { "message": "Tạo mật khẩu" @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "You have not selected anything." }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "Unsubscribe" @@ -4277,7 +4304,7 @@ "message": "Văn bản" }, "createSend": { - "message": "Gửi mới", + "message": "Mục Gửi mới", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -4969,19 +4996,19 @@ "message": "When member(s) are revoked, they no longer have access to organization data. To quickly restore member access, go to the Revoked tab. The process may take a few seconds to complete and cannot be interrupted or canceled." }, "theme": { - "message": "Theme" + "message": "Chủ đề" }, "themeDesc": { - "message": "Choose a theme for your web vault." + "message": "Chọn chủ đề cho kho lưu trữ web của bạn." }, "themeSystem": { - "message": "Use system theme" + "message": "Sử dụng chủ đề của hệ thống" }, "themeDark": { - "message": "Dark" + "message": "Tối" }, "themeLight": { - "message": "Light" + "message": "Sáng" }, "confirmSelected": { "message": "Confirm selected" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "Verification code is required." }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "Invalid verification code" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 462dc05dbfd..4c646b8e7f4 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "持卡人姓名" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "号码" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "验证器密钥 (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "文件夹" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "您的登录会话已过期。" }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "确定要注销吗?" }, @@ -727,7 +754,7 @@ "message": "通行密钥已达上限。移除一个通行密钥以添加另一个。" }, "tryAgain": { - "message": "再试一次" + "message": "请重试" }, "createAccount": { "message": "创建账户" @@ -3043,7 +3070,7 @@ } }, "copiedHiddenFieldItemId": { - "message": "复制了项目 $ID$ 隐藏字段。", + "message": "复制了项目 $ID$ 的隐藏字段。", "placeholders": { "id": { "content": "$1", @@ -3760,7 +3787,7 @@ "message": "订阅" }, "loading": { - "message": "正在加载" + "message": "加载中" }, "upgrade": { "message": "升级" @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "您尚未选择任何内容。" }, - "receiveMarketingEmails": { - "message": "接收来自 Bitwarden 的电子邮件,以获取公告、建议和调研。" + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "取消订阅" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "必须填写验证码。" }, + "webauthnCancelOrTimeout": { + "message": "身份验证被取消或耗时过长。请重试。" + }, "invalidVerificationCode": { "message": "无效的验证码" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "与 Duo 服务连接时出错。使用不同的两步登录方式或联系 Duo 寻求帮助。" + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "启动 Duo 然后按照步骤完成登录。" }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "购买的席位已移除" } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 7120eb05db9..c59c5d77a6b 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -42,6 +42,12 @@ "cardholderName": { "message": "持卡人姓名" }, + "loginCredentials": { + "message": "Login credentials" + }, + "authenticatorKey": { + "message": "Authenticator key" + }, "number": { "message": "號碼" }, @@ -138,6 +144,15 @@ "authenticatorKeyTotp": { "message": "驗證器金鑰 (TOTP)" }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, "folder": { "message": "資料夾" }, @@ -612,6 +627,18 @@ "loginExpired": { "message": "您的登入工作階段已逾期。" }, + "restartRegistration": { + "message": "Restart registration" + }, + "expiredLink": { + "message": "Expired link" + }, + "pleaseRestartRegistrationOrTryLoggingIn": { + "message": "Please restart registration or try logging in." + }, + "youMayAlreadyHaveAnAccount": { + "message": "You may already have an account" + }, "logOutConfirmation": { "message": "您確定要登出嗎?" }, @@ -3783,8 +3810,8 @@ "nothingSelected": { "message": "您未選取任何内容。" }, - "receiveMarketingEmails": { - "message": "Get emails from Bitwarden for announcements, advice, and research opportunities." + "receiveMarketingEmailsV2": { + "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "unsubscribe": { "message": "取消訂閱" @@ -5519,6 +5546,9 @@ "verificationCodeRequired": { "message": "必須填入驗證碼。" }, + "webauthnCancelOrTimeout": { + "message": "The authentication was cancelled or took too long. Please try again." + }, "invalidVerificationCode": { "message": "無效的驗證碼" }, @@ -6210,6 +6240,9 @@ } } }, + "duoHealthCheckResultsInNullAuthUrlError": { + "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + }, "launchDuoAndFollowStepsToFinishLoggingIn": { "message": "Launch Duo and follow the steps to finish logging in." }, @@ -8558,5 +8591,8 @@ "example": "Organization name" } } + }, + "purchasedSeatsRemoved": { + "message": "purchased seats removed" } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts index 1b31341e0d9..413ced840d7 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "@bitwarden/angular/auth/guards"; import { canAccessSettingsTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { IsEnterpriseOrgGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/is-enterprise-org.guard"; +import { isEnterpriseOrgGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/is-enterprise-org.guard"; import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard"; import { OrganizationLayoutComponent } from "@bitwarden/web-vault/app/admin-console/organizations/layouts/organization-layout.component"; @@ -72,7 +72,7 @@ const routes: Routes = [ data: { titleId: "memberAccessReport", }, - canActivate: [IsEnterpriseOrgGuard], + canActivate: [isEnterpriseOrgGuard()], }, ], }, diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html index cd7d41da221..82ca34a89b8 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html @@ -20,10 +20,10 @@ - Members - Groups - Collections - Items + {{ "members" | i18n }} + {{ "groups" | i18n }} + {{ "collections" | i18n }} + {{ "items" | i18n }} diff --git a/jest.config.js b/jest.config.js index 57f9b3c3229..6526237261f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -35,6 +35,7 @@ module.exports = { "/libs/tools/generator/extensions/history/jest.config.js", "/libs/tools/generator/extensions/legacy/jest.config.js", "/libs/tools/generator/extensions/navigation/jest.config.js", + "/libs/tools/send/send-ui/jest.config.js", "/libs/importer/jest.config.js", "/libs/platform/jest.config.js", "/libs/node/jest.config.js", diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.html b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.html new file mode 100644 index 00000000000..34b7ee90395 --- /dev/null +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.html @@ -0,0 +1,6 @@ + +

+ {{ "duoRequiredByOrgForAccount" | i18n }} +

+

{{ "launchDuoAndFollowStepsToFinishLoggingIn" | i18n }}

+
diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.ts new file mode 100644 index 00000000000..1d6b3e26297 --- /dev/null +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.ts @@ -0,0 +1,79 @@ +import { DialogModule } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { ReactiveFormsModule, FormsModule } from "@angular/forms"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { + ButtonModule, + LinkModule, + TypographyModule, + FormFieldModule, + AsyncActionsModule, + ToastService, +} from "@bitwarden/components"; + +@Component({ + standalone: true, + selector: "app-two-factor-auth-duo", + templateUrl: "two-factor-auth-duo.component.html", + imports: [ + CommonModule, + JslibModule, + DialogModule, + ButtonModule, + LinkModule, + TypographyModule, + ReactiveFormsModule, + FormFieldModule, + AsyncActionsModule, + FormsModule, + ], + providers: [I18nPipe], +}) +export class TwoFactorAuthDuoComponent { + @Output() token = new EventEmitter(); + @Input() providerData: any; + + duoFramelessUrl: string = null; + duoResultListenerInitialized = false; + + constructor( + protected i18nService: I18nService, + protected platformUtilsService: PlatformUtilsService, + protected toastService: ToastService, + ) {} + + async ngOnInit(): Promise { + await this.init(); + } + + async init() { + // Setup listener for duo-redirect.ts connector to send back the code + if (!this.duoResultListenerInitialized) { + // setup client specific duo result listener + this.setupDuoResultListener(); + this.duoResultListenerInitialized = true; + } + + // flow must be launched by user so they can choose to remember the device or not. + this.duoFramelessUrl = this.providerData.AuthUrl; + } + + // Each client will have own implementation + protected setupDuoResultListener(): void {} + async launchDuoFrameless(): Promise { + if (this.duoFramelessUrl === null) { + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"), + }); + return; + } + this.platformUtilsService.launchUri(this.duoFramelessUrl); + } +} diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html index 33a5e291faa..137a4f903be 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html @@ -16,6 +16,15 @@ (token)="token = $event; submitForm()" *ngIf="selectedProviderType === providerType.WebAuthn" /> + {{ "rememberMe" | i18n }} @@ -35,10 +44,28 @@ buttonType="primary" bitButton bitFormButton - *ngIf="selectedProviderType != null && selectedProviderType !== providerType.WebAuthn" + *ngIf=" + selectedProviderType != null && + selectedProviderType !== providerType.WebAuthn && + selectedProviderType !== providerType.Duo && + selectedProviderType !== providerType.OrganizationDuo + " > {{ actionButtonText }} + + {{ "cancel" | i18n }} diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts index 16a95d6ba2f..21aaf119c43 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from "@angular/common"; -import { Component, Inject, OnInit } from "@angular/core"; +import { Component, Inject, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; import { ActivatedRoute, NavigationExtras, Router, RouterLink } from "@angular/router"; import { Subject, takeUntil, lastValueFrom, first, firstValueFrom } from "rxjs"; @@ -39,6 +39,7 @@ import { import { CaptchaProtectedComponent } from "../captcha-protected.component"; import { TwoFactorAuthAuthenticatorComponent } from "./two-factor-auth-authenticator.component"; +import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; import { TwoFactorAuthEmailComponent } from "./two-factor-auth-email.component"; import { TwoFactorAuthWebAuthnComponent } from "./two-factor-auth-webauthn.component"; import { TwoFactorAuthYubikeyComponent } from "./two-factor-auth-yubikey.component"; @@ -63,6 +64,7 @@ import { TwoFactorOptionsComponent, TwoFactorAuthAuthenticatorComponent, TwoFactorAuthEmailComponent, + TwoFactorAuthDuoComponent, TwoFactorAuthYubikeyComponent, TwoFactorAuthWebAuthnComponent, ], @@ -78,6 +80,7 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator; providerData: any; + @ViewChild("duoComponent") duoComponent!: TwoFactorAuthDuoComponent; formGroup = this.formBuilder.group({ token: [ "", @@ -220,6 +223,12 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements } } + async launchDuo() { + if (this.duoComponent != null) { + await this.duoComponent.launchDuoFrameless(); + } + } + protected handleMigrateEncryptionKey(result: AuthResult): boolean { if (!result.requiresEncryptionKeyMigration) { return false; diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 1b534b6f779..56b2e956cd1 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1050,6 +1050,7 @@ const safeProviders: SafeProvider[] = [ SECURE_STORAGE, UserDecryptionOptionsServiceAbstraction, LogService, + ConfigService, ], }), safeProvider({ diff --git a/libs/angular/src/tools/generator/components/generator.component.ts b/libs/angular/src/tools/generator/components/generator.component.ts index e17ac7071cd..94bd3fc2876 100644 --- a/libs/angular/src/tools/generator/components/generator.component.ts +++ b/libs/angular/src/tools/generator/components/generator.component.ts @@ -151,9 +151,6 @@ export class GeneratorComponent implements OnInit, OnDestroy { this.usernameOptions.subaddressType = this.usernameOptions.catchallType = "random"; } else { this.usernameOptions.website = this.usernameWebsite; - const websiteOption = { name: this.i18nService.t("websiteName"), value: "website-name" }; - this.subaddressOptions.push(websiteOption); - this.catchallOptions.push(websiteOption); } } @@ -201,6 +198,12 @@ export class GeneratorComponent implements OnInit, OnDestroy { takeUntil(this.destroy$), ), ); + + if (this.usernameWebsite !== null) { + const websiteOption = { name: this.i18nService.t("websiteName"), value: "website-name" }; + this.subaddressOptions.push(websiteOption); + this.catchallOptions.push(websiteOption); + } } ngOnDestroy() { diff --git a/libs/auth/src/angular/icons/registration-expired-link.icon.ts b/libs/auth/src/angular/icons/registration-expired-link.icon.ts new file mode 100644 index 00000000000..50bcc53808f --- /dev/null +++ b/libs/auth/src/angular/icons/registration-expired-link.icon.ts @@ -0,0 +1,20 @@ +import { svgIcon } from "@bitwarden/components"; + +export const RegistrationExpiredLinkIcon = svgIcon` + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/index.ts b/libs/auth/src/angular/index.ts index dec1d7c08b4..b5944772435 100644 --- a/libs/auth/src/angular/index.ts +++ b/libs/auth/src/angular/index.ts @@ -20,6 +20,7 @@ export * from "./user-verification/user-verification-form-input.component"; // registration export * from "./registration/registration-start/registration-start.component"; export * from "./registration/registration-finish/registration-finish.component"; +export * from "./registration/registration-link-expired/registration-link-expired.component"; export * from "./registration/registration-start/registration-start-secondary.component"; export * from "./registration/registration-env-selector/registration-env-selector.component"; export * from "./registration/registration-finish/registration-finish.service"; diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts index 43a8951318e..580b339e1eb 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts @@ -1,10 +1,14 @@ import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Params, Router, RouterModule } from "@angular/router"; -import { Subject, takeUntil } from "rxjs"; +import { Subject, from, switchMap, takeUntil, tap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; +import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; +import { RegisterVerificationEmailClickedRequest } from "@bitwarden/common/auth/models/request/registration/register-verification-email-clicked.request"; +import { HttpStatusCode } from "@bitwarden/common/enums"; +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { ToastService } from "@bitwarden/components"; @@ -41,33 +45,43 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { private i18nService: I18nService, private registrationFinishService: RegistrationFinishService, private validationService: ValidationService, + private accountApiService: AccountApiService, ) {} async ngOnInit() { this.listenForQueryParamChanges(); this.masterPasswordPolicyOptions = await this.registrationFinishService.getMasterPasswordPolicyOptsFromOrgInvite(); - this.loading = false; } private listenForQueryParamChanges() { - this.activatedRoute.queryParams.pipe(takeUntil(this.destroy$)).subscribe((qParams: Params) => { - if (qParams.email != null && qParams.email.indexOf("@") > -1) { - this.email = qParams.email; - } + this.activatedRoute.queryParams + .pipe( + tap((qParams: Params) => { + if (qParams.email != null && qParams.email.indexOf("@") > -1) { + this.email = qParams.email; + } - if (qParams.token != null) { - this.emailVerificationToken = qParams.token; - } + if (qParams.token != null) { + this.emailVerificationToken = qParams.token; + } + }), + switchMap((qParams: Params) => { + if ( + qParams.fromEmail && + qParams.fromEmail === "true" && + this.email && + this.emailVerificationToken + ) { + return from( + this.registerVerificationEmailClicked(this.email, this.emailVerificationToken), + ); + } + }), - if (qParams.fromEmail && qParams.fromEmail === "true") { - this.toastService.showToast({ - title: null, - message: this.i18nService.t("emailVerifiedV2"), - variant: "success", - }); - } - }); + takeUntil(this.destroy$), + ) + .subscribe(); } async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) { @@ -94,6 +108,48 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { await this.router.navigate(["/login"], { queryParams: { email: this.email } }); } + private async registerVerificationEmailClicked(email: string, emailVerificationToken: string) { + const request = new RegisterVerificationEmailClickedRequest(email, emailVerificationToken); + + try { + const result = await this.accountApiService.registerVerificationEmailClicked(request); + + if (result == null) { + this.toastService.showToast({ + title: null, + message: this.i18nService.t("emailVerifiedV2"), + variant: "success", + }); + this.loading = false; + } + } catch (e) { + await this.handleRegisterVerificationEmailClickedError(e); + this.loading = false; + } + } + + private async handleRegisterVerificationEmailClickedError(e: unknown) { + if (e instanceof ErrorResponse) { + const errorResponse = e as ErrorResponse; + switch (errorResponse.statusCode) { + case HttpStatusCode.BadRequest: { + if (errorResponse.message.includes("Expired link")) { + await this.router.navigate(["/signup-link-expired"]); + } else { + this.validationService.showError(errorResponse); + } + + break; + } + default: + this.validationService.showError(errorResponse); + break; + } + } else { + this.validationService.showError(e); + } + } + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); diff --git a/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.html b/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.html new file mode 100644 index 00000000000..5aa6866bbe0 --- /dev/null +++ b/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.html @@ -0,0 +1,27 @@ +
+ + +

+ {{ "pleaseRestartRegistrationOrTryLoggingIn" | i18n }}
+ {{ "youMayAlreadyHaveAnAccount" | i18n }} +

+ + + {{ "restartRegistration" | i18n }} + + + + {{ "logIn" | i18n }} + +
diff --git a/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts b/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts new file mode 100644 index 00000000000..d80aec8351d --- /dev/null +++ b/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts @@ -0,0 +1,44 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute, RouterModule } from "@angular/router"; +import { Subject, firstValueFrom } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ButtonModule, IconModule } from "@bitwarden/components"; + +import { RegistrationExpiredLinkIcon } from "../../icons/registration-expired-link.icon"; + +/** + * RegistrationLinkExpiredComponentData + * @loginRoute: string - The client specific route to the login page - configured at the app-routing.module level. + */ +export interface RegistrationLinkExpiredComponentData { + loginRoute: string; +} + +@Component({ + standalone: true, + selector: "auth-registration-link-expired", + templateUrl: "./registration-link-expired.component.html", + imports: [CommonModule, JslibModule, RouterModule, IconModule, ButtonModule], +}) +export class RegistrationLinkExpiredComponent implements OnInit, OnDestroy { + private destroy$ = new Subject(); + + loginRoute: string; + + readonly Icons = { RegistrationExpiredLinkIcon }; + + constructor(private activatedRoute: ActivatedRoute) {} + + async ngOnInit() { + const routeData = await firstValueFrom(this.activatedRoute.data); + + this.loginRoute = routeData["loginRoute"]; + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.component.html b/libs/auth/src/angular/registration/registration-start/registration-start.component.html index 4bb926093a5..d4298504fce 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.component.html +++ b/libs/auth/src/angular/registration/registration-start/registration-start.component.html @@ -12,7 +12,6 @@ type="email" formControlName="email" [attr.readonly]="emailReadonly ? true : null" - appAutofocus /> @@ -29,7 +28,7 @@ formControlName="receiveMarketingEmails" /> - {{ "receiveMarketingEmails" | i18n }} + {{ "receiveMarketingEmailsV2" | i18n }} {{ "checkYourEmail" | i18n }} diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.component.ts b/libs/auth/src/angular/registration/registration-start/registration-start.component.ts index 00baeb71072..141bff11525 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.component.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start.component.ts @@ -127,9 +127,12 @@ export class RegistrationStartComponent implements OnInit, OnDestroy { return; } + // The app expects null for name and not empty string. + const sanitizedName = this.name.value === "" ? null : this.name.value; + const request: RegisterSendVerificationEmailRequest = new RegisterSendVerificationEmailRequest( this.email.value, - this.name.value, + sanitizedName, this.receiveMarketingEmails.value, ); diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts index 492772081d6..885cad014c6 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts @@ -312,6 +312,27 @@ describe("SsoLoginStrategy", () => { expect(cryptoService.setUserKey).not.toHaveBeenCalled(); }); + it("logs when a device key is found but no decryption keys were recieved in token response", async () => { + // Arrange + const userDecryptionOpts = userDecryptionOptsServerResponseWithTdeOption; + userDecryptionOpts.TrustedDeviceOption.EncryptedPrivateKey = null; + userDecryptionOpts.TrustedDeviceOption.EncryptedUserKey = null; + + const idTokenResponse: IdentityTokenResponse = identityTokenResponseFactory( + null, + userDecryptionOpts, + ); + + apiService.postIdentityToken.mockResolvedValue(idTokenResponse); + deviceTrustService.getDeviceKey.mockResolvedValue(mockDeviceKey); + + // Act + await ssoLoginStrategy.logIn(credentials); + + // Assert + expect(deviceTrustService.recordDeviceTrustLoss).toHaveBeenCalledTimes(1); + }); + describe("AdminAuthRequest", () => { let tokenResponse: IdentityTokenResponse; diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.ts index 2ba0f682b59..5c979c55599 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.ts @@ -296,16 +296,20 @@ export class SsoLoginStrategy extends LoginStrategy { if (!deviceKey || !encDevicePrivateKey || !encUserKey) { if (!deviceKey) { - await this.logService.warning("Unable to set user key due to missing device key."); + this.logService.warning("Unable to set user key due to missing device key."); + } else if (!encDevicePrivateKey || !encUserKey) { + // Tell the server that we have a device key, but received no decryption keys + await this.deviceTrustService.recordDeviceTrustLoss(); } if (!encDevicePrivateKey) { - await this.logService.warning( + this.logService.warning( "Unable to set user key due to missing encrypted device private key.", ); } if (!encUserKey) { - await this.logService.warning("Unable to set user key due to missing encrypted user key."); + this.logService.warning("Unable to set user key due to missing encrypted user key."); } + return; } diff --git a/libs/auth/src/common/services/pin/pin.service.implementation.ts b/libs/auth/src/common/services/pin/pin.service.implementation.ts index 61586fae462..ac2493a8c48 100644 --- a/libs/auth/src/common/services/pin/pin.service.implementation.ts +++ b/libs/auth/src/common/services/pin/pin.service.implementation.ts @@ -405,9 +405,6 @@ export class PinService implements PinServiceAbstraction { await this.clearOldPinKeyEncryptedMasterKey(userId); - // This also clears the old Biometrics key since the new Biometrics key will be created when the user key is set. - await this.stateService.setCryptoMasterKeyBiometric(null, { userId: userId }); - return userKey; } diff --git a/libs/auth/src/common/services/pin/pin.service.spec.ts b/libs/auth/src/common/services/pin/pin.service.spec.ts index b40d37d4246..81009993d2e 100644 --- a/libs/auth/src/common/services/pin/pin.service.spec.ts +++ b/libs/auth/src/common/services/pin/pin.service.spec.ts @@ -455,8 +455,6 @@ describe("PinService", () => { await sut.setUserKeyEncryptedPin(mockUserKeyEncryptedPin, mockUserId); await sut.clearOldPinKeyEncryptedMasterKey(mockUserId); - - await stateService.setCryptoMasterKeyBiometric(null, { userId: mockUserId }); } function mockDecryptUserKeyFn() { diff --git a/libs/common/custom-matchers.d.ts b/libs/common/custom-matchers.d.ts index 214529ff021..5dd30dac79e 100644 --- a/libs/common/custom-matchers.d.ts +++ b/libs/common/custom-matchers.d.ts @@ -1,4 +1,4 @@ -import type { CustomMatchers } from "./test.setup"; +import type { CustomMatchers } from "./spec"; // This declares the types for our custom matchers so that they're recognised by Typescript // This file must also be included in the TS compilation (via the tsconfig.json "include" property) to be recognised by diff --git a/libs/common/spec/matchers/index.ts b/libs/common/spec/matchers/index.ts index 59f6409fefa..235f54d7754 100644 --- a/libs/common/spec/matchers/index.ts +++ b/libs/common/spec/matchers/index.ts @@ -1 +1,57 @@ +import { toBeFulfilled, toBeResolved, toBeRejected } from "./promise-fulfilled"; +import { toAlmostEqual } from "./to-almost-equal"; +import { toEqualBuffer } from "./to-equal-buffer"; + export * from "./to-equal-buffer"; +export * from "./to-almost-equal"; +export * from "./promise-fulfilled"; + +export function addCustomMatchers() { + expect.extend({ + toEqualBuffer: toEqualBuffer, + toAlmostEqual: toAlmostEqual, + toBeFulfilled: toBeFulfilled, + toBeResolved: toBeResolved, + toBeRejected: toBeRejected, + }); +} + +export interface CustomMatchers { + toEqualBuffer(expected: Uint8Array | ArrayBuffer): R; + /** + * Matches the expected date within an optional ms precision + * @param expected The expected date + * @param msPrecision The optional precision in milliseconds + */ + toAlmostEqual(expected: Date, msPrecision?: number): R; + /** + * Matches whether the received promise has been fulfilled. + * + * Failure if the promise is not currently fulfilled. + * + * @param received The promise to test + * @param withinMs The time within the promise should be fulfilled. Defaults to 0, indicating that the promise should already be fulfilled + * @returns CustomMatcherResult indicating whether or not the test passed + */ + toBeFulfilled(withinMs?: number): Promise; + /** + * Matches whether the received promise has been resolved. + * + * Failure if the promise is not currently fulfilled or if it has been rejected. + * + * @param received The promise to test + * @param withinMs The time within the promise should be resolved. Defaults to 0, indicating that the promise should already be resolved + * @returns CustomMatcherResult indicating whether or not the test passed + */ + toBeResolved(withinMs?: number): Promise; + /** + * Matches whether the received promise has been rejected. + * + * Failure if the promise is not currently fulfilled or if it has been resolved, but not rejected. + * + * @param received The promise to test + * @param withinMs The time within the promise should be rejected. Defaults to 0, indicating that the promise should already be rejected + * @returns CustomMatcherResult indicating whether or not the test passed + */ + toBeRejected(withinMs?: number): Promise; +} diff --git a/libs/common/spec/matchers/promise-fulfilled.spec.ts b/libs/common/spec/matchers/promise-fulfilled.spec.ts new file mode 100644 index 00000000000..cbb3147174b --- /dev/null +++ b/libs/common/spec/matchers/promise-fulfilled.spec.ts @@ -0,0 +1,86 @@ +describe("toBeFulfilled", () => { + it("passes when promise is resolved", async () => { + const promise = Promise.resolve("resolved"); + await promise; + await expect(promise).toBeFulfilled(); + }); + + it("passes when promise is rejected", async () => { + const promise = Promise.reject("rejected"); + await promise.catch(() => {}); + await expect(promise).toBeFulfilled(); + }); + + it("fails when promise is pending", async () => { + const promise = new Promise((resolve) => setTimeout(resolve, 1000)); + await expect(promise).not.toBeFulfilled(); + }); + + it("passes when the promise is fulfilled within the given time limit", async () => { + const promise = new Promise((resolve) => setTimeout(resolve, 1)); + await expect(promise).toBeFulfilled(25); + }); + + it("passes when the promise is not fulfilled within the given time limit", async () => { + const promise = new Promise(() => {}); + await expect(promise).not.toBeFulfilled(1); + }); +}); + +describe("toBeResolved", () => { + it("passes when promise is resolved", async () => { + const promise = Promise.resolve("resolved"); + await promise; + await expect(promise).toBeResolved(); + }); + + it("fails when promise is rejected", async () => { + const promise = Promise.reject("rejected"); + await promise.catch(() => {}); + await expect(promise).not.toBeResolved(); + }); + + it("fails when promise is pending", async () => { + const promise = new Promise((resolve) => setTimeout(resolve, 1000)); + await expect(promise).not.toBeResolved(); + }); + + it("passes when the promise is resolved within the given time limit", async () => { + const promise = new Promise((resolve) => setTimeout(resolve, 1)); + await expect(promise).toBeResolved(50); + }); + + it("passes when the promise is not resolved within the given time limit", async () => { + const promise = new Promise(() => {}); + await expect(promise).not.toBeResolved(1); + }); +}); + +describe("toBeRejected", () => { + it("fails when promise is resolved", async () => { + const promise = Promise.resolve("resolved"); + await promise; + await expect(promise).not.toBeRejected(); + }); + + it("passes when promise is rejected", async () => { + const promise = Promise.reject("rejected"); + await promise.catch(() => {}); + await expect(promise).toBeRejected(); + }); + + it("fails when promise is pending", async () => { + const promise = new Promise((resolve) => setTimeout(resolve, 1000)); + await expect(promise).not.toBeRejected(); + }); + + it("passes when the promise is resolved within the given time limit", async () => { + const promise = new Promise((_, reject) => setTimeout(reject, 1)); + await expect(promise).toBeFulfilled(50); + }); + + it("passes when the promise is not resolved within the given time limit", async () => { + const promise = new Promise(() => {}); + await expect(promise).not.toBeFulfilled(1); + }); +}); diff --git a/libs/common/spec/matchers/promise-fulfilled.ts b/libs/common/spec/matchers/promise-fulfilled.ts new file mode 100644 index 00000000000..c29c608a2cc --- /dev/null +++ b/libs/common/spec/matchers/promise-fulfilled.ts @@ -0,0 +1,79 @@ +async function wait(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** + * Matches whether the received promise has been fulfilled. + * + * Failure if the promise is not currently fulfilled. + * + * @param received The promise to test + * @param withinMs The time within the promise should be fulfilled. Defaults to 0, indicating that the promise should already be fulfilled + * @returns CustomMatcherResult indicating whether or not the test passed + */ +export const toBeFulfilled: jest.CustomMatcher = async function ( + received: Promise, + withinMs = 0, +) { + return { + pass: await Promise.race([ + wait(withinMs).then(() => false), + received.then( + () => true, + () => true, + ), + ]), + message: () => `expected promise to be fulfilled`, + }; +}; + +/** + * Matches whether the received promise has been resolved. + * + * Failure if the promise is not currently fulfilled or if it has been rejected. + * + * @param received The promise to test + * @param withinMs The time within the promise should be resolved. Defaults to 0, indicating that the promise should already be resolved + * @returns CustomMatcherResult indicating whether or not the test passed + + */ +export const toBeResolved: jest.CustomMatcher = async function ( + received: Promise, + withinMs = 0, +) { + return { + pass: await Promise.race([ + wait(withinMs).then(() => false), + received.then( + () => true, + () => false, + ), + ]), + message: () => `expected promise to be resolved`, + }; +}; + +/** + * Matches whether the received promise has been rejected. + * + * Failure if the promise is not currently fulfilled or if it has been resolved, but not rejected. + * + * @param received The promise to test + * @param withinMs The time within the promise should be rejected. Defaults to 0, indicating that the promise should already be rejected + * @returns CustomMatcherResult indicating whether or not the test passed + */ +export const toBeRejected: jest.CustomMatcher = async function ( + received: Promise, + withinMs = 0, +) { + return { + pass: await Promise.race([ + wait(withinMs).then(() => false), + received.then( + () => false, + () => true, + ), + ]), + message: () => `expected promise to be rejected`, + }; +}; diff --git a/libs/common/src/admin-console/models/data/organization.data.spec.ts b/libs/common/src/admin-console/models/data/organization.data.spec.ts index 549c759f939..7b0d1ea2e99 100644 --- a/libs/common/src/admin-console/models/data/organization.data.spec.ts +++ b/libs/common/src/admin-console/models/data/organization.data.spec.ts @@ -53,7 +53,6 @@ describe("ORGANIZATIONS state", () => { accessSecretsManager: false, limitCollectionCreationDeletion: false, allowAdminAccessToAllCollectionItems: false, - flexibleCollections: false, familySponsorshipLastSyncDate: new Date(), }, }; diff --git a/libs/common/src/admin-console/models/data/organization.data.ts b/libs/common/src/admin-console/models/data/organization.data.ts index e93ada435e0..77f7908caf5 100644 --- a/libs/common/src/admin-console/models/data/organization.data.ts +++ b/libs/common/src/admin-console/models/data/organization.data.ts @@ -54,7 +54,6 @@ export class OrganizationData { accessSecretsManager: boolean; limitCollectionCreationDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; - flexibleCollections: boolean; constructor( response?: ProfileOrganizationResponse, @@ -113,7 +112,6 @@ export class OrganizationData { this.accessSecretsManager = response.accessSecretsManager; this.limitCollectionCreationDeletion = response.limitCollectionCreationDeletion; this.allowAdminAccessToAllCollectionItems = response.allowAdminAccessToAllCollectionItems; - this.flexibleCollections = response.flexibleCollections; this.isMember = options.isMember; this.isProviderUser = options.isProviderUser; diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts index d8e90e41be7..dcffe6f1588 100644 --- a/libs/common/src/admin-console/models/domain/organization.ts +++ b/libs/common/src/admin-console/models/domain/organization.ts @@ -73,11 +73,6 @@ export class Organization { * Refers to the ability for an owner/admin to access all collection items, regardless of assigned collections */ allowAdminAccessToAllCollectionItems: boolean; - /** - * Returns true if this organization has enabled Flexible Collections (MVP) and their data has been migrated. - * Generally, you should use this as the feature flag to gate Flexible Collections features. - */ - flexibleCollections: boolean; constructor(obj?: OrganizationData) { if (obj == null) { @@ -132,7 +127,6 @@ export class Organization { this.accessSecretsManager = obj.accessSecretsManager; this.limitCollectionCreationDeletion = obj.limitCollectionCreationDeletion; this.allowAdminAccessToAllCollectionItems = obj.allowAdminAccessToAllCollectionItems; - this.flexibleCollections = obj.flexibleCollections; } get canAccess() { diff --git a/libs/common/src/admin-console/models/response/organization.response.ts b/libs/common/src/admin-console/models/response/organization.response.ts index 3e05c9110a5..b47aecf4537 100644 --- a/libs/common/src/admin-console/models/response/organization.response.ts +++ b/libs/common/src/admin-console/models/response/organization.response.ts @@ -34,7 +34,6 @@ export class OrganizationResponse extends BaseResponse { maxAutoscaleSmServiceAccounts?: number; limitCollectionCreationDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; - flexibleCollections: boolean; constructor(response: any) { super(response); @@ -76,6 +75,5 @@ export class OrganizationResponse extends BaseResponse { this.allowAdminAccessToAllCollectionItems = this.getResponseProperty( "AllowAdminAccessToAllCollectionItems", ); - this.flexibleCollections = this.getResponseProperty("FlexibleCollections"); } } diff --git a/libs/common/src/admin-console/models/response/profile-organization.response.ts b/libs/common/src/admin-console/models/response/profile-organization.response.ts index 01c39f98800..693a7db4eb3 100644 --- a/libs/common/src/admin-console/models/response/profile-organization.response.ts +++ b/libs/common/src/admin-console/models/response/profile-organization.response.ts @@ -51,7 +51,6 @@ export class ProfileOrganizationResponse extends BaseResponse { accessSecretsManager: boolean; limitCollectionCreationDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; - flexibleCollections: boolean; constructor(response: any) { super(response); @@ -116,6 +115,5 @@ export class ProfileOrganizationResponse extends BaseResponse { this.allowAdminAccessToAllCollectionItems = this.getResponseProperty( "AllowAdminAccessToAllCollectionItems", ); - this.flexibleCollections = this.getResponseProperty("FlexibleCollections"); } } diff --git a/libs/common/src/admin-console/services/policy/policy.service.ts b/libs/common/src/admin-console/services/policy/policy.service.ts index e36902cbf94..7ec525f9606 100644 --- a/libs/common/src/admin-console/services/policy/policy.service.ts +++ b/libs/common/src/admin-console/services/policy/policy.service.ts @@ -235,6 +235,9 @@ export class PolicyService implements InternalPolicyServiceAbstraction { case PolicyType.PasswordGenerator: // password generation policy applies to everyone return false; + case PolicyType.PersonalOwnership: + // individual vault policy applies to everyone except admins and owners + return organization.isAdmin; default: return organization.canManagePolicies; } diff --git a/libs/common/src/auth/abstractions/account-api.service.ts b/libs/common/src/auth/abstractions/account-api.service.ts index f1f1c5471e0..78fbb2cf882 100644 --- a/libs/common/src/auth/abstractions/account-api.service.ts +++ b/libs/common/src/auth/abstractions/account-api.service.ts @@ -1,5 +1,6 @@ import { RegisterFinishRequest } from "../models/request/registration/register-finish.request"; import { RegisterSendVerificationEmailRequest } from "../models/request/registration/register-send-verification-email.request"; +import { RegisterVerificationEmailClickedRequest } from "../models/request/registration/register-verification-email-clicked.request"; import { Verification } from "../types/verification"; export abstract class AccountApiService { @@ -26,6 +27,19 @@ export abstract class AccountApiService { request: RegisterSendVerificationEmailRequest, ): Promise; + /** + * Raises a server event to identify when users click the email verification link and land + * on the registration finish screen. + * + * @param request - The request object containing the email verification token and the + * user's email address (which is required to validate the token) + * @returns A promise that resolves when the event is logged on the server succcessfully or a bad + * request if the token is invalid for any reason. + */ + abstract registerVerificationEmailClicked( + request: RegisterVerificationEmailClickedRequest, + ): Promise; + /** * Completes the registration process. * diff --git a/libs/common/src/auth/abstractions/device-trust.service.abstraction.ts b/libs/common/src/auth/abstractions/device-trust.service.abstraction.ts index 123f7103386..4c52945554c 100644 --- a/libs/common/src/auth/abstractions/device-trust.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/device-trust.service.abstraction.ts @@ -32,4 +32,9 @@ export abstract class DeviceTrustServiceAbstraction { newUserKey: UserKey, masterPasswordHash: string, ) => Promise; + /** + * Notifies the server that the device has a device key, but didn't receive any associated decryption keys. + * Note: For debugging purposes only. + */ + recordDeviceTrustLoss: () => Promise; } diff --git a/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts b/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts index 7ab929a2c28..8c3d0c3015d 100644 --- a/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/devices-api.service.abstraction.ts @@ -27,4 +27,11 @@ export abstract class DevicesApiServiceAbstraction { deviceIdentifier: string, secretVerificationRequest: SecretVerificationRequest, ) => Promise; + + /** + * Notifies the server that the device has a device key, but didn't receive any associated decryption keys. + * Note: For debugging purposes only. + * @param deviceIdentifier - current device identifier + */ + postDeviceTrustLoss: (deviceIdentifier: string) => Promise; } diff --git a/libs/common/src/auth/models/request/registration/register-verification-email-clicked.request.ts b/libs/common/src/auth/models/request/registration/register-verification-email-clicked.request.ts new file mode 100644 index 00000000000..942a6726cd6 --- /dev/null +++ b/libs/common/src/auth/models/request/registration/register-verification-email-clicked.request.ts @@ -0,0 +1,6 @@ +export class RegisterVerificationEmailClickedRequest { + constructor( + public email: string, + public emailVerificationToken: string, + ) {} +} diff --git a/libs/common/src/auth/services/account-api.service.ts b/libs/common/src/auth/services/account-api.service.ts index 194e63bea6c..e10b0686f61 100644 --- a/libs/common/src/auth/services/account-api.service.ts +++ b/libs/common/src/auth/services/account-api.service.ts @@ -9,6 +9,7 @@ import { InternalAccountService } from "../abstractions/account.service"; import { UserVerificationService } from "../abstractions/user-verification/user-verification.service.abstraction"; import { RegisterFinishRequest } from "../models/request/registration/register-finish.request"; import { RegisterSendVerificationEmailRequest } from "../models/request/registration/register-send-verification-email.request"; +import { RegisterVerificationEmailClickedRequest } from "../models/request/registration/register-verification-email-clicked.request"; import { Verification } from "../types/verification"; export class AccountApiServiceImplementation implements AccountApiService { @@ -60,6 +61,28 @@ export class AccountApiServiceImplementation implements AccountApiService { } } + async registerVerificationEmailClicked( + request: RegisterVerificationEmailClickedRequest, + ): Promise { + const env = await firstValueFrom(this.environmentService.environment$); + + try { + const response = await this.apiService.send( + "POST", + "/accounts/register/verification-email-clicked", + request, + false, + false, + env.getIdentityUrl(), + ); + + return response; + } catch (e: unknown) { + this.logService.error(e); + throw e; + } + } + async registerFinish(request: RegisterFinishRequest): Promise { const env = await firstValueFrom(this.environmentService.environment$); diff --git a/libs/common/src/auth/services/device-trust.service.implementation.ts b/libs/common/src/auth/services/device-trust.service.implementation.ts index b02ff114489..cc80ea888c3 100644 --- a/libs/common/src/auth/services/device-trust.service.implementation.ts +++ b/libs/common/src/auth/services/device-trust.service.implementation.ts @@ -2,7 +2,9 @@ import { firstValueFrom, map, Observable } from "rxjs"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +import { FeatureFlag } from "../../enums/feature-flag.enum"; import { AppIdService } from "../../platform/abstractions/app-id.service"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service"; import { CryptoService } from "../../platform/abstractions/crypto.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; @@ -68,6 +70,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { private secureStorageService: AbstractStorageService, private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private logService: LogService, + private configService: ConfigService, ) { this.supportsDeviceTrust$ = this.userDecryptionOptionsService.userDecryptionOptions$.pipe( map((options) => options?.trustedDeviceOption != null ?? false), @@ -287,6 +290,16 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { throw new Error("UserId is required. Cannot decrypt user key with device key."); } + if (!encryptedDevicePrivateKey) { + throw new Error( + "Encrypted device private key is required. Cannot decrypt user key with device key.", + ); + } + + if (!encryptedUserKey) { + throw new Error("Encrypted user key is required. Cannot decrypt user key with device key."); + } + if (!deviceKey) { // User doesn't have a device key anymore so device is untrusted return null; @@ -315,6 +328,14 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { } } + async recordDeviceTrustLoss(): Promise { + if (!(await this.configService.getFeatureFlag(FeatureFlag.DeviceTrustLogging))) { + return; + } + const deviceIdentifier = await this.appIdService.getAppId(); + await this.devicesApiService.postDeviceTrustLoss(deviceIdentifier); + } + private getSecureStorageOptions(userId: UserId): StorageOptions { return { storageLocation: StorageLocation.Disk, diff --git a/libs/common/src/auth/services/device-trust.service.spec.ts b/libs/common/src/auth/services/device-trust.service.spec.ts index 7cc4de8b2db..7afd38ec0ad 100644 --- a/libs/common/src/auth/services/device-trust.service.spec.ts +++ b/libs/common/src/auth/services/device-trust.service.spec.ts @@ -9,6 +9,7 @@ import { FakeActiveUserState } from "../../../spec/fake-state"; import { FakeStateProvider } from "../../../spec/fake-state-provider"; import { DeviceType } from "../../enums"; import { AppIdService } from "../../platform/abstractions/app-id.service"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; import { CryptoFunctionService } from "../../platform/abstractions/crypto-function.service"; import { CryptoService } from "../../platform/abstractions/crypto.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; @@ -50,6 +51,7 @@ describe("deviceTrustService", () => { const platformUtilsService = mock(); const secureStorageService = mock(); const logService = mock(); + const configService = mock(); const userDecryptionOptionsService = mock(); const decryptionOptions = new BehaviorSubject(null); @@ -533,6 +535,32 @@ describe("deviceTrustService", () => { ).rejects.toThrow("UserId is required. Cannot decrypt user key with device key."); }); + it("throws an error when a nullish encrypted device private key is passed in", async () => { + await expect( + deviceTrustService.decryptUserKeyWithDeviceKey( + mockUserId, + null, + mockEncryptedUserKey, + mockDeviceKey, + ), + ).rejects.toThrow( + "Encrypted device private key is required. Cannot decrypt user key with device key.", + ); + }); + + it("throws an error when a nullish encrypted user key is passed in", async () => { + await expect( + deviceTrustService.decryptUserKeyWithDeviceKey( + mockUserId, + mockEncryptedDevicePrivateKey, + null, + mockDeviceKey, + ), + ).rejects.toThrow( + "Encrypted user key is required. Cannot decrypt user key with device key.", + ); + }); + it("returns null when device key isn't provided", async () => { const result = await deviceTrustService.decryptUserKeyWithDeviceKey( mockUserId, @@ -731,6 +759,7 @@ describe("deviceTrustService", () => { secureStorageService, userDecryptionOptionsService, logService, + configService, ); } }); diff --git a/libs/common/src/auth/services/devices-api.service.implementation.ts b/libs/common/src/auth/services/devices-api.service.implementation.ts index 5ecd2874582..fbef5c782e4 100644 --- a/libs/common/src/auth/services/devices-api.service.implementation.ts +++ b/libs/common/src/auth/services/devices-api.service.implementation.ts @@ -101,4 +101,18 @@ export class DevicesApiServiceImplementation implements DevicesApiServiceAbstrac ); return new ProtectedDeviceResponse(result); } + + async postDeviceTrustLoss(deviceIdentifier: string): Promise { + await this.apiService.send( + "POST", + "/devices/lost-trust", + null, + true, + false, + null, + (headers) => { + headers.set("Device-Identifier", deviceIdentifier); + }, + ); + } } diff --git a/libs/common/src/autofill/constants/index.ts b/libs/common/src/autofill/constants/index.ts index efbd0896428..d3b5e6ee108 100644 --- a/libs/common/src/autofill/constants/index.ts +++ b/libs/common/src/autofill/constants/index.ts @@ -61,3 +61,27 @@ export const AutofillOverlayVisibility = { OnButtonClick: 1, OnFieldFocus: 2, } as const; + +export const BrowserClientVendors = { + Chrome: "Chrome", + Opera: "Opera", + Edge: "Edge", + Vivaldi: "Vivaldi", + Unknown: "Unknown", +} as const; + +export const BrowserShortcutsUris = { + Chrome: "chrome://extensions/shortcuts", + Opera: "opera://extensions/shortcuts", + Edge: "edge://extensions/shortcuts", + Vivaldi: "vivaldi://extensions/shortcuts", + Unknown: "https://bitwarden.com/help/keyboard-shortcuts", +} as const; + +export const DisablePasswordManagerUris = { + Chrome: "chrome://settings/autofill", + Opera: "opera://settings/autofill", + Edge: "edge://settings/passwords", + Vivaldi: "vivaldi://settings/autofill", + Unknown: "https://bitwarden.com/help/disable-browser-autofill/", +} as const; diff --git a/libs/common/src/autofill/types/index.ts b/libs/common/src/autofill/types/index.ts index be5d98f4e06..9a5a434d9e2 100644 --- a/libs/common/src/autofill/types/index.ts +++ b/libs/common/src/autofill/types/index.ts @@ -1,7 +1,18 @@ -import { ClearClipboardDelay, AutofillOverlayVisibility } from "../constants"; +import { + AutofillOverlayVisibility, + BrowserClientVendors, + BrowserShortcutsUris, + ClearClipboardDelay, + DisablePasswordManagerUris, +} from "../constants"; export type ClearClipboardDelaySetting = (typeof ClearClipboardDelay)[keyof typeof ClearClipboardDelay]; export type InlineMenuVisibilitySetting = (typeof AutofillOverlayVisibility)[keyof typeof AutofillOverlayVisibility]; + +export type BrowserClientVendor = (typeof BrowserClientVendors)[keyof typeof BrowserClientVendors]; +export type BrowserShortcutsUri = (typeof BrowserShortcutsUris)[keyof typeof BrowserShortcutsUris]; +export type DisablePasswordManagerUri = + (typeof DisablePasswordManagerUris)[keyof typeof DisablePasswordManagerUris]; diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 7e88af236fa..b5a617bc7e2 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -26,6 +26,7 @@ export enum FeatureFlag { ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner", VaultBulkManagementAction = "vault-bulk-management-action", AC2828_ProviderPortalMembersPage = "AC-2828_provider-portal-members-page", + DeviceTrustLogging = "pm-8285-device-trust-logging", } export type AllowedFeatureFlagTypes = boolean | number | string; @@ -62,6 +63,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.ProviderClientVaultPrivacyBanner]: FALSE, [FeatureFlag.VaultBulkManagementAction]: FALSE, [FeatureFlag.AC2828_ProviderPortalMembersPage]: FALSE, + [FeatureFlag.DeviceTrustLogging]: FALSE, } satisfies Record; export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue; diff --git a/libs/common/src/models/domain/domain-service.ts b/libs/common/src/models/domain/domain-service.ts index d5247765fc1..9ff53cc8787 100644 --- a/libs/common/src/models/domain/domain-service.ts +++ b/libs/common/src/models/domain/domain-service.ts @@ -20,5 +20,6 @@ export const UriMatchStrategy = { export type UriMatchStrategySetting = (typeof UriMatchStrategy)[keyof typeof UriMatchStrategy]; -export type NeverDomains = { [id: string]: unknown }; +// using uniqueness properties of object shape over Set for ease of state storability +export type NeverDomains = { [id: string]: null }; export type EquivalentDomains = string[][]; diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 5c06fdda508..d9498905bbe 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -56,18 +56,6 @@ export abstract class StateService { * @deprecated For migration purposes only, use setUserKeyAuto instead */ setCryptoMasterKeyAuto: (value: string, options?: StorageOptions) => Promise; - /** - * @deprecated For migration purposes only, use getUserKeyBiometric instead - */ - getCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise; - /** - * @deprecated For migration purposes only, use hasUserKeyBiometric instead - */ - hasCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise; - /** - * @deprecated For migration purposes only, use setUserKeyBiometric instead - */ - setCryptoMasterKeyBiometric: (value: BiometricKey, options?: StorageOptions) => Promise; getDuckDuckGoSharedKey: (options?: StorageOptions) => Promise; setDuckDuckGoSharedKey: (value: string, options?: StorageOptions) => Promise; getIsAuthenticated: (options?: StorageOptions) => Promise; diff --git a/libs/common/src/platform/models/domain/account.ts b/libs/common/src/platform/models/domain/account.ts index 79ba4058a41..b367d617dac 100644 --- a/libs/common/src/platform/models/domain/account.ts +++ b/libs/common/src/platform/models/domain/account.ts @@ -49,8 +49,6 @@ export class AccountKeys { /** @deprecated July 2023, left for migration purposes*/ cryptoMasterKeyAuto?: string; /** @deprecated July 2023, left for migration purposes*/ - cryptoMasterKeyBiometric?: string; - /** @deprecated July 2023, left for migration purposes*/ cryptoSymmetricKey?: EncryptionPair = new EncryptionPair< string, SymmetricCryptoKey diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index e799471241f..ba5abecaac6 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -33,7 +33,6 @@ const partialKeys = { userBiometricKey: "_user_biometric", autoKey: "_masterkey_auto", - biometricKey: "_masterkey_biometric", masterKey: "_masterkey", }; @@ -254,54 +253,6 @@ export class StateService< await this.saveSecureStorageKey(partialKeys.masterKey, value, options); } - /** - * @deprecated Use UserKeyBiometric instead - */ - async getCryptoMasterKeyBiometric(options?: StorageOptions): Promise { - options = this.reconcileOptions( - this.reconcileOptions(options, { keySuffix: "biometric" }), - await this.defaultSecureStorageOptions(), - ); - if (options?.userId == null) { - return null; - } - return await this.secureStorageService.get( - `${options.userId}${partialKeys.biometricKey}`, - options, - ); - } - - /** - * @deprecated Use UserKeyBiometric instead - */ - async hasCryptoMasterKeyBiometric(options?: StorageOptions): Promise { - options = this.reconcileOptions( - this.reconcileOptions(options, { keySuffix: "biometric" }), - await this.defaultSecureStorageOptions(), - ); - if (options?.userId == null) { - return false; - } - return await this.secureStorageService.has( - `${options.userId}${partialKeys.biometricKey}`, - options, - ); - } - - /** - * @deprecated Use UserKeyBiometric instead - */ - async setCryptoMasterKeyBiometric(value: BiometricKey, options?: StorageOptions): Promise { - options = this.reconcileOptions( - this.reconcileOptions(options, { keySuffix: "biometric" }), - await this.defaultSecureStorageOptions(), - ); - if (options?.userId == null) { - return; - } - await this.saveSecureStorageKey(partialKeys.biometricKey, value, options); - } - async getDuckDuckGoSharedKey(options?: StorageOptions): Promise { options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); if (options?.userId == null) { @@ -678,7 +629,6 @@ export class StateService< await this.setUserKeyAutoUnlock(null, { userId: userId }); await this.setUserKeyBiometric(null, { userId: userId }); await this.setCryptoMasterKeyAuto(null, { userId: userId }); - await this.setCryptoMasterKeyBiometric(null, { userId: userId }); await this.setCryptoMasterKeyB64(null, { userId: userId }); } diff --git a/libs/common/src/platform/state/implementations/default-single-user-state.spec.ts b/libs/common/src/platform/state/implementations/default-single-user-state.spec.ts index b1216c0bafa..3f51828a59f 100644 --- a/libs/common/src/platform/state/implementations/default-single-user-state.spec.ts +++ b/libs/common/src/platform/state/implementations/default-single-user-state.spec.ts @@ -100,6 +100,24 @@ describe("DefaultSingleUserState", () => { ); expect(state).toBeTruthy(); }); + + it("should go to disk each subscription if a cleanupDelayMs of 0 is given", async () => { + const state = new DefaultSingleUserState( + userId, + new UserKeyDefinition(testStateDefinition, "test", { + cleanupDelayMs: 0, + deserializer: TestState.fromJSON, + clearOn: [], + }), + diskStorageService, + stateEventRegistrarService, + ); + + await firstValueFrom(state.state$); + await firstValueFrom(state.state$); + + expect(diskStorageService.mock.get).toHaveBeenCalledTimes(2); + }); }); describe("combinedState$", () => { diff --git a/libs/common/src/platform/state/implementations/state-base.ts b/libs/common/src/platform/state/implementations/state-base.ts index c09771033cc..9beab1200a1 100644 --- a/libs/common/src/platform/state/implementations/state-base.ts +++ b/libs/common/src/platform/state/implementations/state-base.ts @@ -48,15 +48,22 @@ export abstract class StateBase> }), ); - this.state$ = merge( + let state$ = merge( defer(() => getStoredValue(key, storageService, keyDefinition.deserializer)), storageUpdate$, - ).pipe( - share({ - connector: () => new ReplaySubject(1), - resetOnRefCountZero: () => timer(keyDefinition.cleanupDelayMs), - }), ); + + // If 0 cleanup is chosen, treat this as absolutely no cache + if (keyDefinition.cleanupDelayMs !== 0) { + state$ = state$.pipe( + share({ + connector: () => new ReplaySubject(1), + resetOnRefCountZero: () => timer(keyDefinition.cleanupDelayMs), + }), + ); + } + + this.state$ = state$; } async update( diff --git a/libs/common/src/platform/state/key-definition.spec.ts b/libs/common/src/platform/state/key-definition.spec.ts index ee926bccd8e..f68fb6f5ab6 100644 --- a/libs/common/src/platform/state/key-definition.spec.ts +++ b/libs/common/src/platform/state/key-definition.spec.ts @@ -38,12 +38,12 @@ describe("KeyDefinition", () => { expect(keyDefinition.cleanupDelayMs).toBe(500); }); - it.each([0, -1])("throws on 0 or negative (%s)", (testValue: number) => { + it("throws on negative", () => { expect( () => new KeyDefinition(fakeStateDefinition, "fake", { deserializer: (value) => value, - cleanupDelayMs: testValue, + cleanupDelayMs: -1, }), ).toThrow(); }); diff --git a/libs/common/src/platform/state/key-definition.ts b/libs/common/src/platform/state/key-definition.ts index bdabd8df50b..ea6ed4b2f30 100644 --- a/libs/common/src/platform/state/key-definition.ts +++ b/libs/common/src/platform/state/key-definition.ts @@ -50,9 +50,9 @@ export class KeyDefinition { throw new Error(`'deserializer' is a required property on key ${this.errorKeyName}`); } - if (options.cleanupDelayMs <= 0) { + if (options.cleanupDelayMs < 0) { throw new Error( - `'cleanupDelayMs' must be greater than 0. Value of ${options.cleanupDelayMs} passed to key ${this.errorKeyName} `, + `'cleanupDelayMs' must be greater than or equal to 0. Value of ${options.cleanupDelayMs} passed to key ${this.errorKeyName} `, ); } } diff --git a/libs/common/src/platform/state/user-key-definition.ts b/libs/common/src/platform/state/user-key-definition.ts index 7e845fc8585..503cd7b7828 100644 --- a/libs/common/src/platform/state/user-key-definition.ts +++ b/libs/common/src/platform/state/user-key-definition.ts @@ -30,9 +30,9 @@ export class UserKeyDefinition { throw new Error(`'deserializer' is a required property on key ${this.errorKeyName}`); } - if (options.cleanupDelayMs <= 0) { + if (options.cleanupDelayMs < 0) { throw new Error( - `'cleanupDelayMs' must be greater than 0. Value of ${options.cleanupDelayMs} passed to key ${this.errorKeyName} `, + `'cleanupDelayMs' must be greater than or equal to 0. Value of ${options.cleanupDelayMs} passed to key ${this.errorKeyName} `, ); } diff --git a/libs/common/src/types/guid.ts b/libs/common/src/types/guid.ts index 97c87e684e9..59dbe28f907 100644 --- a/libs/common/src/types/guid.ts +++ b/libs/common/src/types/guid.ts @@ -8,4 +8,5 @@ export type CollectionId = Opaque; export type ProviderId = Opaque; export type PolicyId = Opaque; export type CipherId = Opaque; +export type SendId = Opaque; export type IndexedEntityId = Opaque; diff --git a/libs/common/src/vault/linked-field-option.decorator.ts b/libs/common/src/vault/linked-field-option.decorator.ts index 9f296c92cd7..91fe1ee28f1 100644 --- a/libs/common/src/vault/linked-field-option.decorator.ts +++ b/libs/common/src/vault/linked-field-option.decorator.ts @@ -1,11 +1,30 @@ import { LinkedIdType } from "./enums"; import { ItemView } from "./models/view/item.view"; +type LinkedMetadataAttributes = { + /** + * The i18n key used to describe the decorated class property in the UI. + * If it is null, then the name of the class property will be used as the i18n key. + */ + i18nKey?: string; + + /** + * The position of the individual field to be applied when sorted. + */ + sortPosition: number; +}; + export class LinkedMetadata { + private readonly _i18nKey: string; + readonly sortPosition: number; + constructor( readonly propertyKey: string, - private readonly _i18nKey?: string, - ) {} + attributes: LinkedMetadataAttributes, + ) { + this._i18nKey = attributes?.i18nKey; + this.sortPosition = attributes.sortPosition; + } get i18nKey() { return this._i18nKey ?? this.propertyKey; @@ -16,15 +35,14 @@ export class LinkedMetadata { * A decorator used to set metadata used by Linked custom fields. Apply it to a class property or getter to make it * available as a Linked custom field option. * @param id - A unique value that is saved in the Field model. It is used to look up the decorated class property. - * @param i18nKey - The i18n key used to describe the decorated class property in the UI. If it is null, then the name - * of the class property will be used as the i18n key. + * @param options - {@link LinkedMetadataAttributes} */ -export function linkedFieldOption(id: LinkedIdType, i18nKey?: string) { +export function linkedFieldOption(id: LinkedIdType, attributes: LinkedMetadataAttributes) { return (prototype: ItemView, propertyKey: string) => { if (prototype.linkedFieldOptions == null) { prototype.linkedFieldOptions = new Map(); } - prototype.linkedFieldOptions.set(id, new LinkedMetadata(propertyKey, i18nKey)); + prototype.linkedFieldOptions.set(id, new LinkedMetadata(propertyKey, attributes)); }; } diff --git a/libs/common/src/vault/models/domain/collection.spec.ts b/libs/common/src/vault/models/domain/collection.spec.ts index ecbb1df0833..2b01a3ff036 100644 --- a/libs/common/src/vault/models/domain/collection.spec.ts +++ b/libs/common/src/vault/models/domain/collection.spec.ts @@ -42,7 +42,7 @@ describe("Collection", () => { id: "id", organizationId: "orgId", name: { encryptedString: "encName", encryptionType: 0 }, - externalId: "extId", + externalId: { encryptedString: "extId", encryptionType: 0 }, readOnly: true, manage: true, hidePasswords: true, diff --git a/libs/common/src/vault/models/domain/collection.ts b/libs/common/src/vault/models/domain/collection.ts index 89da8bef880..6a9e71f1109 100644 --- a/libs/common/src/vault/models/domain/collection.ts +++ b/libs/common/src/vault/models/domain/collection.ts @@ -31,7 +31,7 @@ export class Collection extends Domain { hidePasswords: null, manage: null, }, - ["id", "organizationId", "externalId", "readOnly", "hidePasswords", "manage"], + ["id", "organizationId", "readOnly", "hidePasswords", "manage"], ); } diff --git a/libs/common/src/vault/models/view/card.view.ts b/libs/common/src/vault/models/view/card.view.ts index c45d27968ec..d83b2c6f0a8 100644 --- a/libs/common/src/vault/models/view/card.view.ts +++ b/libs/common/src/vault/models/view/card.view.ts @@ -6,13 +6,13 @@ import { linkedFieldOption } from "../../linked-field-option.decorator"; import { ItemView } from "./item.view"; export class CardView extends ItemView { - @linkedFieldOption(LinkedId.CardholderName) + @linkedFieldOption(LinkedId.CardholderName, { sortPosition: 0 }) cardholderName: string = null; - @linkedFieldOption(LinkedId.ExpMonth, "expirationMonth") + @linkedFieldOption(LinkedId.ExpMonth, { sortPosition: 3, i18nKey: "expirationMonth" }) expMonth: string = null; - @linkedFieldOption(LinkedId.ExpYear, "expirationYear") + @linkedFieldOption(LinkedId.ExpYear, { sortPosition: 4, i18nKey: "expirationYear" }) expYear: string = null; - @linkedFieldOption(LinkedId.Code, "securityCode") + @linkedFieldOption(LinkedId.Code, { sortPosition: 5, i18nKey: "securityCode" }) code: string = null; private _brand: string = null; @@ -27,7 +27,7 @@ export class CardView extends ItemView { return this.number != null ? "•".repeat(this.number.length) : null; } - @linkedFieldOption(LinkedId.Brand) + @linkedFieldOption(LinkedId.Brand, { sortPosition: 2 }) get brand(): string { return this._brand; } @@ -36,7 +36,7 @@ export class CardView extends ItemView { this._subTitle = null; } - @linkedFieldOption(LinkedId.Number) + @linkedFieldOption(LinkedId.Number, { sortPosition: 1 }) get number(): string { return this._number; } diff --git a/libs/common/src/vault/models/view/identity.view.ts b/libs/common/src/vault/models/view/identity.view.ts index 02db81b9290..8854b8664e6 100644 --- a/libs/common/src/vault/models/view/identity.view.ts +++ b/libs/common/src/vault/models/view/identity.view.ts @@ -7,37 +7,37 @@ import { linkedFieldOption } from "../../linked-field-option.decorator"; import { ItemView } from "./item.view"; export class IdentityView extends ItemView { - @linkedFieldOption(LinkedId.Title) + @linkedFieldOption(LinkedId.Title, { sortPosition: 0 }) title: string = null; - @linkedFieldOption(LinkedId.MiddleName) + @linkedFieldOption(LinkedId.MiddleName, { sortPosition: 2 }) middleName: string = null; - @linkedFieldOption(LinkedId.Address1) + @linkedFieldOption(LinkedId.Address1, { sortPosition: 12 }) address1: string = null; - @linkedFieldOption(LinkedId.Address2) + @linkedFieldOption(LinkedId.Address2, { sortPosition: 13 }) address2: string = null; - @linkedFieldOption(LinkedId.Address3) + @linkedFieldOption(LinkedId.Address3, { sortPosition: 14 }) address3: string = null; - @linkedFieldOption(LinkedId.City, "cityTown") + @linkedFieldOption(LinkedId.City, { sortPosition: 15, i18nKey: "cityTown" }) city: string = null; - @linkedFieldOption(LinkedId.State, "stateProvince") + @linkedFieldOption(LinkedId.State, { sortPosition: 16, i18nKey: "stateProvince" }) state: string = null; - @linkedFieldOption(LinkedId.PostalCode, "zipPostalCode") + @linkedFieldOption(LinkedId.PostalCode, { sortPosition: 17, i18nKey: "zipPostalCode" }) postalCode: string = null; - @linkedFieldOption(LinkedId.Country) + @linkedFieldOption(LinkedId.Country, { sortPosition: 18 }) country: string = null; - @linkedFieldOption(LinkedId.Company) + @linkedFieldOption(LinkedId.Company, { sortPosition: 6 }) company: string = null; - @linkedFieldOption(LinkedId.Email) + @linkedFieldOption(LinkedId.Email, { sortPosition: 10 }) email: string = null; - @linkedFieldOption(LinkedId.Phone) + @linkedFieldOption(LinkedId.Phone, { sortPosition: 11 }) phone: string = null; - @linkedFieldOption(LinkedId.Ssn) + @linkedFieldOption(LinkedId.Ssn, { sortPosition: 7 }) ssn: string = null; - @linkedFieldOption(LinkedId.Username) + @linkedFieldOption(LinkedId.Username, { sortPosition: 5 }) username: string = null; - @linkedFieldOption(LinkedId.PassportNumber) + @linkedFieldOption(LinkedId.PassportNumber, { sortPosition: 8 }) passportNumber: string = null; - @linkedFieldOption(LinkedId.LicenseNumber) + @linkedFieldOption(LinkedId.LicenseNumber, { sortPosition: 9 }) licenseNumber: string = null; private _firstName: string = null; @@ -48,7 +48,7 @@ export class IdentityView extends ItemView { super(); } - @linkedFieldOption(LinkedId.FirstName) + @linkedFieldOption(LinkedId.FirstName, { sortPosition: 1 }) get firstName(): string { return this._firstName; } @@ -57,7 +57,7 @@ export class IdentityView extends ItemView { this._subTitle = null; } - @linkedFieldOption(LinkedId.LastName) + @linkedFieldOption(LinkedId.LastName, { sortPosition: 4 }) get lastName(): string { return this._lastName; } @@ -83,7 +83,7 @@ export class IdentityView extends ItemView { return this._subTitle; } - @linkedFieldOption(LinkedId.FullName) + @linkedFieldOption(LinkedId.FullName, { sortPosition: 3 }) get fullName(): string { if ( this.title != null || diff --git a/libs/common/src/vault/models/view/login.view.ts b/libs/common/src/vault/models/view/login.view.ts index 1236e047b69..2f525b24136 100644 --- a/libs/common/src/vault/models/view/login.view.ts +++ b/libs/common/src/vault/models/view/login.view.ts @@ -10,9 +10,9 @@ import { ItemView } from "./item.view"; import { LoginUriView } from "./login-uri.view"; export class LoginView extends ItemView { - @linkedFieldOption(LinkedId.Username) + @linkedFieldOption(LinkedId.Username, { sortPosition: 0 }) username: string = null; - @linkedFieldOption(LinkedId.Password) + @linkedFieldOption(LinkedId.Password, { sortPosition: 1 }) password: string = null; passwordRevisionDate?: Date = null; diff --git a/libs/common/src/vault/services/collection.service.ts b/libs/common/src/vault/services/collection.service.ts index 97b3257ab56..096edb77f99 100644 --- a/libs/common/src/vault/services/collection.service.ts +++ b/libs/common/src/vault/services/collection.service.ts @@ -100,6 +100,7 @@ export class CollectionService implements CollectionServiceAbstraction { collection.id = model.id; collection.organizationId = model.organizationId; collection.readOnly = model.readOnly; + collection.externalId = model.externalId; collection.name = await this.cryptoService.encrypt(model.name, key); return collection; } diff --git a/libs/common/src/vault/services/folder/folder.service.ts b/libs/common/src/vault/services/folder/folder.service.ts index 17d9f39f8ec..7de7222edca 100644 --- a/libs/common/src/vault/services/folder/folder.service.ts +++ b/libs/common/src/vault/services/folder/folder.service.ts @@ -137,16 +137,14 @@ export class FolderService implements InternalFolderServiceAbstraction { return; } - if (typeof id === "string") { - if (folders[id] == null) { - return; + const folderIdsToDelete = Array.isArray(id) ? id : [id]; + + folderIdsToDelete.forEach((id) => { + if (folders[id] != null) { + delete folders[id]; } - delete folders[id]; - } else { - (id as string[]).forEach((i) => { - delete folders[i]; - }); - } + }); + return folders; }); diff --git a/libs/common/test.setup.ts b/libs/common/test.setup.ts index d857751b51b..aa71b3e508a 100644 --- a/libs/common/test.setup.ts +++ b/libs/common/test.setup.ts @@ -1,25 +1,10 @@ import { webcrypto } from "crypto"; -import { toEqualBuffer } from "./spec"; -import { toAlmostEqual } from "./spec/matchers/to-almost-equal"; +import { addCustomMatchers } from "./spec"; Object.defineProperty(window, "crypto", { value: webcrypto, }); // Add custom matchers - -expect.extend({ - toEqualBuffer: toEqualBuffer, - toAlmostEqual: toAlmostEqual, -}); - -export interface CustomMatchers { - toEqualBuffer(expected: Uint8Array | ArrayBuffer): R; - /** - * Matches the expected date within an optional ms precision - * @param expected The expected date - * @param msPrecision The optional precision in milliseconds - */ - toAlmostEqual(expected: Date, msPrecision?: number): R; -} +addCustomMatchers(); diff --git a/libs/components/src/section/section.component.ts b/libs/components/src/section/section.component.ts index a60e232eec7..953da04044a 100644 --- a/libs/components/src/section/section.component.ts +++ b/libs/components/src/section/section.component.ts @@ -1,14 +1,21 @@ +import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, Input } from "@angular/core"; @Component({ selector: "bit-section", standalone: true, imports: [CommonModule], template: ` -
+
`, }) -export class SectionComponent {} +export class SectionComponent { + @Input({ transform: coerceBooleanProperty }) disableMargin = false; +} diff --git a/libs/components/src/section/section.mdx b/libs/components/src/section/section.mdx index 33e640e4f06..52672ad59fb 100644 --- a/libs/components/src/section/section.mdx +++ b/libs/components/src/section/section.mdx @@ -13,6 +13,19 @@ import { SectionComponent, SectionHeaderComponent } from "@bitwarden/components" Sections are simple containers that apply a responsive bottom margin and utilize the semantic `section` HTML element. +```html + +
Lots of amazing content!
+
+``` + +To remove the bottom margin (for example, if the section is the last item on the page), you can pass +the `disableMargin` input. + +```html + +``` + @@ -24,7 +37,7 @@ Sections often contain a heading. Use `bit-section-header` inside of the `bit-se ```html -

I'm a section header

+

I'm a section header

Section content here!
diff --git a/libs/components/src/section/section.stories.ts b/libs/components/src/section/section.stories.ts index 0f720d1dba0..0d36d6e5a11 100644 --- a/libs/components/src/section/section.stories.ts +++ b/libs/components/src/section/section.stories.ts @@ -41,6 +41,12 @@ export const Default: Story = {

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex.

+ + +

Baz

+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex.

+
`, }), }; diff --git a/libs/components/src/select/select.component.html b/libs/components/src/select/select.component.html index 2609496e10a..f334e67d691 100644 --- a/libs/components/src/select/select.component.html +++ b/libs/components/src/select/select.component.html @@ -10,11 +10,11 @@ appendTo="body" > -
+
-
+
{{ item.label }}
diff --git a/libs/components/src/toggle-group/toggle.component.ts b/libs/components/src/toggle-group/toggle.component.ts index 7d227acde3e..b07ecd82a8d 100644 --- a/libs/components/src/toggle-group/toggle.component.ts +++ b/libs/components/src/toggle-group/toggle.component.ts @@ -17,7 +17,7 @@ export class ToggleComponent { constructor(private groupComponent: ToggleGroupComponent) {} @HostBinding("tabIndex") tabIndex = "-1"; - @HostBinding("class") classList = ["tw-group"]; + @HostBinding("class") classList = ["tw-group/toggle"]; get name() { return this.groupComponent.name; @@ -28,7 +28,7 @@ export class ToggleComponent { } get inputClasses() { - return ["tw-peer", "tw-appearance-none", "tw-outline-none"]; + return ["tw-peer/toggle-input", "tw-appearance-none", "tw-outline-none"]; } get labelClasses() { @@ -43,27 +43,27 @@ export class ToggleComponent { "tw-border-r", "tw-border-l-0", "tw-cursor-pointer", - "group-first-of-type:tw-border-l", - "group-first-of-type:tw-rounded-l", - "group-last-of-type:tw-rounded-r", + "group-first-of-type/toggle:tw-border-l", + "group-first-of-type/toggle:tw-rounded-l", + "group-last-of-type/toggle:tw-rounded-r", - "peer-focus:tw-outline-none", - "peer-focus:tw-ring", - "peer-focus:tw-ring-offset-2", - "peer-focus:tw-ring-primary-600", - "peer-focus:tw-z-10", - "peer-focus:tw-bg-primary-600", - "peer-focus:tw-border-primary-600", - "peer-focus:!tw-text-contrast", + "peer-focus/toggle-input:tw-outline-none", + "peer-focus/toggle-input:tw-ring", + "peer-focus/toggle-input:tw-ring-offset-2", + "peer-focus/toggle-input:tw-ring-primary-600", + "peer-focus/toggle-input:tw-z-10", + "peer-focus/toggle-input:tw-bg-primary-600", + "peer-focus/toggle-input:tw-border-primary-600", + "peer-focus/toggle-input:!tw-text-contrast", "hover:tw-no-underline", "hover:tw-bg-text-muted", "hover:tw-border-text-muted", "hover:!tw-text-contrast", - "peer-checked:tw-bg-primary-600", - "peer-checked:tw-border-primary-600", - "peer-checked:!tw-text-contrast", + "peer-checked/toggle-input:tw-bg-primary-600", + "peer-checked/toggle-input:tw-border-primary-600", + "peer-checked/toggle-input:!tw-text-contrast", "tw-py-1.5", "tw-px-3", diff --git a/libs/importer/src/components/import.component.html b/libs/importer/src/components/import.component.html index 54df0ba4d27..da39290f6c4 100644 --- a/libs/importer/src/components/import.component.html +++ b/libs/importer/src/components/import.component.html @@ -2,396 +2,419 @@ {{ "personalOwnershipPolicyInEffectImports" | i18n }}
- - {{ "importDestination" | i18n }} - - - - - - - - - + + +

{{ "destination" | i18n }}

+
+ + + {{ "vault" | i18n }} + + + + + + + {{ organizationId ? ("collection" | i18n) : ("folder" | i18n) }} + + + + + + + + + + + {{ + "importTargetHint" + | i18n + : (organizationId ? ("collection" | i18n | lowercase) : ("folder" | i18n | lowercase)) + }} + + +
- - {{ organizationId ? ("collection" | i18n) : ("folder" | i18n) }} - - - - - - - - - - - {{ - "importTargetHint" - | i18n: (organizationId ? ("collection" | i18n | lowercase) : ("folder" | i18n | lowercase)) - }} - - - - {{ "fileFormat" | i18n }} - - - - - - - - - - - - - See detailed instructions on our help site at - - https://bitwarden.com/help/export-your-data/ - - -

- {{ "seeDetailedInstructions" | i18n }} - - https://bitwarden.com/help/import-from-lastpass/ -

- - - {{ "importDirectlyFromLastPass" | i18n }} - - - {{ "importFromCSV" | i18n }} - - -
- - Using the KeePassX desktop application, navigate to "Database" → "Export to CSV file" and - save the CSV file. - - - In the Avira web vault, go to "Settings" → "My Data" → "Export data" and save the - CSV file. - - - In the Blur web vault, click your username at the top and go to "Settings" → "Export - Data", then click "Export CSV" for your "Accounts". - - - Using the SaveInCloud desktop application, navigate to "File" → "Export" → "As XML" - and save the XML file. - - - Using the Padlock desktop application, click the hamburger icon in the top left corner and - navigate to "Settings" → "Export" button and save the file "As CSV". - - - Using the KeePass 2 desktop application, navigate to "File" → "Export" and select the - "KeePass XML (2.x)" option. - - - Using the Universal Password Manager desktop application, navigate to "Database" → - "Export" and save the CSV file. - - - Using the SaferPass browser extension, click the hamburger icon in the top left corner and - navigate to "Settings". Click the "Export accounts" button to save the CSV file. - - - Using the Meldium web vault, navigate to "Settings". Locate the "Export data" function and - click "Show me my data" to save the CSV file. - - - Log into the Keeper web vault (keepersecurity.com/vault). Click on your "account email" (top - right) and select "Settings". Go to "Export" and find the "Export to .csv File" option. Click - "Export" to save the CSV file. - - - - - The process is exactly the same as importing from Google Chrome. - - See detailed instructions on our help site at - - https://bitwarden.com/help/import-from-chrome/ - - - See detailed instructions on our help site at - - https://bitwarden.com/help/import-from-firefox/. - - - See detailed instructions on our help site at - - https://bitwarden.com/help/import-from-safari/. - - - See detailed instructions on our help site at - - https://bitwarden.com/help/import-from-1password/. - - - Using the Password Dragon desktop application, navigate to "File" → "Export" → "To - XML". In the dialog that pops up select "All Rows" and check all fields. Click the "Export" - button and save the XML file. - - - Using the Enpass desktop application, navigate to "File" → "Export" → "As CSV". - Select "OK" to the warning alert and save the CSV file. Note that the importer only supports - files exported while Enpass is set to the English language, so adjust your settings - accordingly. - - - Using the Enpass 6 desktop application, click the menu button and navigate to "File" → - "Export". Select the ".json" file format option and save the JSON file. - - - Using the Password Safe desktop application, navigate to "File" → "Export To" → "XML - format..." and save the XML file. - - - Log in to Dashlane, click on "My Account" → "Settings" → "Export file" and select - "Export as a CSV file". This will download a zip archive containing various CSV files. Unzip - the archive and import each CSV file individually. - - - Dashlane no longer supports the JSON format. Only use this if you have an existing JSON for - import. Use the CSV importer when creating new exports. - - - Using the mSecure desktop application, navigate to "File" → "Export" → "CSV File..." - and save the CSV file. - - - Using the Sticky Password desktop application, navigate to "Menu" (top right) → "Export" - → "Export all". Select the unencrypted format XML option and save the XML file. - - - Using the True Key desktop application, click the gear icon (top right) and then navigate to - "App Settings". Click the "Export" button, enter your password and save the CSV file. - - - Log into the Clipperz web application (clipperz.is/app). Click the hamburger menu icon in the - top right to expand the navigation bar. Navigate to "Data" → "Export". Click the - "download HTML+JSON" button to save the HTML file. - - - Using the RoboForm Editor desktop application, navigate to "RoboForm" (top left) → - "Options" → "Account & Data" and click the "Export" button. Select all of your data, - change the "Format" to "CSV file" and then click the "Export" button to save the CSV file. - Note: RoboForm only allows you to export Logins. Other items will not be exported. - - - Log into the Passbolt web vault and navigate to the "Passwords" listing. Select all of the - passwords you would like to export and click the "Export" button at the top of the listing. - Choose the "csv (lastpass)" export format and click the "Export" button. - - - Using the Ascendo DataVault desktop application, navigate to "Tools" → "Export". In the - dialog that pops up, select the "All Items (DVX, CSV)" option. Click the "Ok" button to save - the CSV file. - - - Using the Password Boss desktop application, navigate to "File" → "Export data" → - "Password Boss JSON - not encrypted" and save the JSON file. - - - Log into the Zoho web vault (vault.zoho.com). Navigate to "Tools" → "Export Secrets". - Select "All Secrets" and click the "Zoho Vault Format CSV" button. Highlight and copy the data - from the textarea. Open a text editor like Notepad and paste the data. Save the data from the - text editor as - zoho_export.csv. - - - Using the SplashID Safe desktop application, click on the SplashID blue lock logo in the top - right corner. Navigate to "Export" → "Export as CSV" and save the CSV file. - - - Using the PassKeep mobile app, navigate to "Backup/Restore". Locate the "CSV Backup/Restore" - section and click "Backup to CSV" to save the CSV file. - - - Make sure you have python-keyring and python-gnomekeyring installed. Save the - GNOME Keyring Import/Export - python script to your desktop as pw_helper.py. Open terminal and run - chmod +rx Desktop/pw_helper.py and then - python Desktop/pw_helper.py export Desktop/my_passwords.json. Then upload the - resulting my_passwords.json file here to Bitwarden. - - - Using the Password Agent desktop application navigate to "File" → "Export", select the - "Fields to export" button and check all of the fields, change the "Output format" to "CSV", - and then click the "Start" button to save the CSV file. - - - Log into the Passpack website vault and navigate to "Settings" → "Export", then click the - "Download" button to save the CSV file. - - - Open your Passman vault and click on "Settings" in the bottom left corner. In the "Settings" - window switch to the "Export credentials" tab and choose "JSON" as the export type. Enter your - vault's passphrase and click the "Export" button to save the JSON file. - - - Open the Avast Passwords desktop application and navigate to "Settings" → "Import/export - data". Select the "Export" button for the "Export to CSV file" option to save the CSV file. - - - Open the Avast Passwords desktop application and navigate to "Settings" → "Import/export - data". Select the "Export" button for the "Export to JSON file" option to save the JSON file. - - - Open the F-Secure KEY desktop application and navigate to "Settings" → "Export - Passwords". Select the "Export" button, enter your master password, and save the FSK file. - - - Open the Kaspersky Password Manager desktop application and navigate to "Settings" → - "Import/Export". Locate the "Export to text file" section and select the "Export" button to - save the TXT file. - - - Open the RememBear desktop application and navigate to "Settings" → "Account" → - "Export". Enter your master password and select the "Export Anyway" button to save the CSV - file. - - - Open the PasswordWallet desktop application and navigate to "File" → "Export" → - "Visible entries to text file". Enter your password and select the "Ok" button to save the TXT - file. - - - Open the Myki desktop browser extension and navigate to "Advanced" → "Export Accounts" - and then scan the QR code with your mobile device. Various CSV files will then be saved to - your computer's downloads folder. - - - Export your SecureSafe password safe to a CSV file with a comma delimiter. - - - Open the LogMeOnce browser extension, then navigate to "Open Menu" → "Export To" and - select "CSV File" to save the CSV file. - - - Open the BlackBerry Password Keeper application, then navigate to "Settings" → - "Import/Export". Select "Export Passwords" and follow the instructions on screen to save the - unencrypted CSV file. - - - Open the Buttercup desktop application and unlock your vault. Right click on your vault's icon - and select "Export" to save the CSV file. - - - Open the Codebook desktop application and log in. Navigate to "File" → "Export all", then - click "Yes" on the dialog and save the CSV file. - - - Open the newest version of the Encryptr desktop application and allow all of your data to - sync. Once syncing of your data is complete, the download icon in the top right corner will - turn pink. Click the download icon and save the CSV file. - - - From the Yoti browser extension, click on "Settings", then "Export Saved Logins" and save the - CSV file. - - - Log in to the Psono web vault, click on the "Signed in as"-dropdown, select "Others". Go to - the "Export"-tab and select the json type export and then click on Export. - - - Log in to "https://vault.passky.org" → "Import & Export" → "Export" in the Passky - section. ("Backup" is unsupported as it is encrypted). - - - In the ProtonPass browser extension, go to Settings > Export. Export without PGP encryption - and save the zip file. - -
- -
- - {{ "selectImportFile" | i18n }} -
- - {{ this.fileSelected ? this.fileSelected.name : ("noFileChosen" | i18n) }} + + + The process is exactly the same as importing from Google Chrome. + + See detailed instructions on our help site at + + https://bitwarden.com/help/import-from-chrome/ + + + See detailed instructions on our help site at + + https://bitwarden.com/help/import-from-firefox/. + + + See detailed instructions on our help site at + + https://bitwarden.com/help/import-from-safari/. + + + See detailed instructions on our help site at + + https://bitwarden.com/help/import-from-1password/. + + + Using the Password Dragon desktop application, navigate to "File" → "Export" → + "To XML". In the dialog that pops up select "All Rows" and check all fields. Click the + "Export" button and save the XML file. + + + Using the Enpass desktop application, navigate to "File" → "Export" → "As CSV". + Select "OK" to the warning alert and save the CSV file. Note that the importer only + supports files exported while Enpass is set to the English language, so adjust your + settings accordingly. + + + Using the Enpass 6 desktop application, click the menu button and navigate to "File" + → "Export". Select the ".json" file format option and save the JSON file. + + + Using the Password Safe desktop application, navigate to "File" → "Export To" → + "XML format..." and save the XML file. + + + Log in to Dashlane, click on "My Account" → "Settings" → "Export file" and + select "Export as a CSV file". This will download a zip archive containing various CSV + files. Unzip the archive and import each CSV file individually. + + + Dashlane no longer supports the JSON format. Only use this if you have an existing JSON + for import. Use the CSV importer when creating new exports. + + + Using the mSecure desktop application, navigate to "File" → "Export" → "CSV + File..." and save the CSV file. + + + Using the Sticky Password desktop application, navigate to "Menu" (top right) → + "Export" → "Export all". Select the unencrypted format XML option and save the XML + file. + + + Using the True Key desktop application, click the gear icon (top right) and then navigate + to "App Settings". Click the "Export" button, enter your password and save the CSV file. + + + Log into the Clipperz web application (clipperz.is/app). Click the hamburger menu icon in + the top right to expand the navigation bar. Navigate to "Data" → "Export". Click the + "download HTML+JSON" button to save the HTML file. + + + Using the RoboForm Editor desktop application, navigate to "RoboForm" (top left) → + "Options" → "Account & Data" and click the "Export" button. Select all of your + data, change the "Format" to "CSV file" and then click the "Export" button to save the CSV + file. Note: RoboForm only allows you to export Logins. Other items will not be exported. + + + Log into the Passbolt web vault and navigate to the "Passwords" listing. Select all of the + passwords you would like to export and click the "Export" button at the top of the + listing. Choose the "csv (lastpass)" export format and click the "Export" button. + + + Using the Ascendo DataVault desktop application, navigate to "Tools" → "Export". In + the dialog that pops up, select the "All Items (DVX, CSV)" option. Click the "Ok" button + to save the CSV file. + + + Using the Password Boss desktop application, navigate to "File" → "Export data" + → "Password Boss JSON - not encrypted" and save the JSON file. + + + Log into the Zoho web vault (vault.zoho.com). Navigate to "Tools" → "Export Secrets". + Select "All Secrets" and click the "Zoho Vault Format CSV" button. Highlight and copy the + data from the textarea. Open a text editor like Notepad and paste the data. Save the data + from the text editor as + zoho_export.csv. + + + Using the SplashID Safe desktop application, click on the SplashID blue lock logo in the + top right corner. Navigate to "Export" → "Export as CSV" and save the CSV file. + + + Using the PassKeep mobile app, navigate to "Backup/Restore". Locate the "CSV + Backup/Restore" section and click "Backup to CSV" to save the CSV file. + + + Make sure you have python-keyring and python-gnomekeyring installed. Save the + GNOME Keyring Import/Export + python script to your desktop as pw_helper.py. Open terminal and run + chmod +rx Desktop/pw_helper.py and then + python Desktop/pw_helper.py export Desktop/my_passwords.json. Then upload the + resulting my_passwords.json file here to Bitwarden. + + + Using the Password Agent desktop application navigate to "File" → "Export", select + the "Fields to export" button and check all of the fields, change the "Output format" to + "CSV", and then click the "Start" button to save the CSV file. + + + Log into the Passpack website vault and navigate to "Settings" → "Export", then click + the "Download" button to save the CSV file. + + + Open your Passman vault and click on "Settings" in the bottom left corner. In the + "Settings" window switch to the "Export credentials" tab and choose "JSON" as the export + type. Enter your vault's passphrase and click the "Export" button to save the JSON file. + + + Open the Avast Passwords desktop application and navigate to "Settings" → + "Import/export data". Select the "Export" button for the "Export to CSV file" option to + save the CSV file. + + + Open the Avast Passwords desktop application and navigate to "Settings" → + "Import/export data". Select the "Export" button for the "Export to JSON file" option to + save the JSON file. + + + Open the F-Secure KEY desktop application and navigate to "Settings" → "Export + Passwords". Select the "Export" button, enter your master password, and save the FSK file. + + + Open the Kaspersky Password Manager desktop application and navigate to "Settings" → + "Import/Export". Locate the "Export to text file" section and select the "Export" button + to save the TXT file. + + + Open the RememBear desktop application and navigate to "Settings" → "Account" → + "Export". Enter your master password and select the "Export Anyway" button to save the CSV + file. + + + Open the PasswordWallet desktop application and navigate to "File" → "Export" → + "Visible entries to text file". Enter your password and select the "Ok" button to save the + TXT file. + + + Open the Myki desktop browser extension and navigate to "Advanced" → "Export + Accounts" and then scan the QR code with your mobile device. Various CSV files will then + be saved to your computer's downloads folder. + + + Export your SecureSafe password safe to a CSV file with a comma delimiter. + + + Open the LogMeOnce browser extension, then navigate to "Open Menu" → "Export To" and + select "CSV File" to save the CSV file. + + + Open the BlackBerry Password Keeper application, then navigate to "Settings" → + "Import/Export". Select "Export Passwords" and follow the instructions on screen to save + the unencrypted CSV file. + + + Open the Buttercup desktop application and unlock your vault. Right click on your vault's + icon and select "Export" to save the CSV file. + + + Open the Codebook desktop application and log in. Navigate to "File" → "Export all", + then click "Yes" on the dialog and save the CSV file. + + + Open the newest version of the Encryptr desktop application and allow all of your data to + sync. Once syncing of your data is complete, the download icon in the top right corner + will turn pink. Click the download icon and save the CSV file. + + + From the Yoti browser extension, click on "Settings", then "Export Saved Logins" and save + the CSV file. + + + Log in to the Psono web vault, click on the "Signed in as"-dropdown, select "Others". Go + to the "Export"-tab and select the json type export and then click on Export. + + + Log in to "https://vault.passky.org" → "Import & Export" → "Export" in the + Passky section. ("Backup" is unsupported as it is encrypted). + + + In the ProtonPass browser extension, go to Settings > Export. Export without PGP + encryption and save the zip file. + + + +
+ + {{ "selectImportFile" | i18n }} +
+ + {{ this.fileSelected ? this.fileSelected.name : ("noFileChosen" | i18n) }} +
+ +
+ + {{ "orCopyPasteFileContents" | i18n }} + +
- - - - {{ "orCopyPasteFileContents" | i18n }} - - -
+ + diff --git a/libs/importer/src/components/import.component.ts b/libs/importer/src/components/import.component.ts index 8f3566831d1..4670cb2f754 100644 --- a/libs/importer/src/components/import.component.ts +++ b/libs/importer/src/components/import.component.ts @@ -43,10 +43,14 @@ import { BitSubmitDirective, ButtonModule, CalloutModule, + CardComponent, + ContainerComponent, DialogService, FormFieldModule, IconButtonModule, RadioButtonModule, + SectionComponent, + SectionHeaderComponent, SelectModule, ToastService, } from "@bitwarden/components"; @@ -104,6 +108,10 @@ const safeProviders: SafeProvider[] = [ ReactiveFormsModule, ImportLastPassComponent, RadioButtonModule, + CardComponent, + ContainerComponent, + SectionHeaderComponent, + SectionComponent, ], providers: safeProviders, }) diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts index 84d4c459348..cf6577c403e 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts +++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts @@ -9,7 +9,7 @@ import { ViewChild, } from "@angular/core"; import { ReactiveFormsModule, UntypedFormBuilder, Validators } from "@angular/forms"; -import { map, merge, Observable, startWith, Subject, takeUntil } from "rxjs"; +import { combineLatest, map, merge, Observable, startWith, Subject, takeUntil } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { PasswordStrengthV2Component } from "@bitwarden/angular/tools/password-strength/password-strength-v2.component"; @@ -25,6 +25,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncryptedExportType } from "@bitwarden/common/tools/enums/encrypted-export-type.enum"; +import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { AsyncActionsModule, BitSubmitDirective, @@ -160,6 +161,7 @@ export class ExportComponent implements OnInit, OnDestroy { protected fileDownloadService: FileDownloadService, protected dialogService: DialogService, protected organizationService: OrganizationService, + private collectionService: CollectionService, ) {} async ngOnInit() { @@ -196,8 +198,21 @@ export class ExportComponent implements OnInit, OnDestroy { return; } - this.organizations$ = this.organizationService.memberOrganizations$.pipe( - map((orgs) => orgs.sort(Utils.getSortFunction(this.i18nService, "name"))), + this.organizations$ = combineLatest({ + collections: this.collectionService.decryptedCollections$, + memberOrganizations: this.organizationService.memberOrganizations$, + }).pipe( + map(({ collections, memberOrganizations }) => { + const managedCollectionsOrgIds = new Set( + collections.filter((c) => c.manage).map((c) => c.organizationId), + ); + // Filter organizations that exist in managedCollectionsOrgIds + const filteredOrgs = memberOrganizations.filter((org) => + managedCollectionsOrgIds.has(org.id), + ); + // Sort the filtered organizations based on the name + return filteredOrgs.sort(Utils.getSortFunction(this.i18nService, "name")); + }), ); this.exportForm.controls.vaultSelector.valueChanges diff --git a/libs/tools/generator/core/src/abstractions/index.ts b/libs/tools/generator/core/src/abstractions/index.ts index 5eee745372e..471ec89ea32 100644 --- a/libs/tools/generator/core/src/abstractions/index.ts +++ b/libs/tools/generator/core/src/abstractions/index.ts @@ -1,4 +1,4 @@ export { GeneratorService } from "./generator.service.abstraction"; export { GeneratorStrategy } from "./generator-strategy.abstraction"; export { PolicyEvaluator } from "./policy-evaluator.abstraction"; -export { Randomizer } from "./randomizer"; +export { Randomizer } from "../engine/abstractions"; diff --git a/libs/tools/generator/core/src/data/default-password-generation-options.ts b/libs/tools/generator/core/src/data/default-password-generation-options.ts index 00dd60c6fd7..1c26fd8f951 100644 --- a/libs/tools/generator/core/src/data/default-password-generation-options.ts +++ b/libs/tools/generator/core/src/data/default-password-generation-options.ts @@ -8,7 +8,9 @@ export const DefaultPasswordGenerationOptions: Partial { + const cryptoService = mock(); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("pick", () => { + it.each([[null], [undefined], [[]]])("throws when the list is %p", async (list) => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + + await expect(() => randomizer.pick(list)).rejects.toBeInstanceOf(Error); + + expect.assertions(1); + }); + + it("picks an item from the list", async () => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + cryptoService.randomNumber.mockResolvedValue(1); + + const result = await randomizer.pick([0, 1]); + + expect(result).toBe(1); + }); + }); + + describe("pickWord", () => { + it.each([[null], [undefined], [[]]])("throws when the list is %p", async (list) => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + + await expect(() => randomizer.pickWord(list)).rejects.toBeInstanceOf(Error); + + expect.assertions(1); + }); + + it("picks a word from the list", async () => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + cryptoService.randomNumber.mockResolvedValue(1); + + const result = await randomizer.pickWord(["foo", "bar"]); + + expect(result).toBe("bar"); + }); + + it("capitalizes the word when options.titleCase is true", async () => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + cryptoService.randomNumber.mockResolvedValue(1); + + const result = await randomizer.pickWord(["foo", "bar"], { titleCase: true }); + + expect(result).toBe("Bar"); + }); + + it("appends a random number when options.number is true", async () => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + cryptoService.randomNumber.mockResolvedValueOnce(1); + cryptoService.randomNumber.mockResolvedValueOnce(2); + + const result = await randomizer.pickWord(["foo", "bar"], { number: true }); + + expect(result).toBe("bar2"); + }); + }); + + describe("shuffle", () => { + it.each([[null], [undefined], [[]]])("throws when the list is %p", async (list) => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + + await expect(() => randomizer.shuffle(list)).rejects.toBeInstanceOf(Error); + + expect.assertions(1); + }); + + it("returns a copy of the list without shuffling it when theres only one entry", async () => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + + const result = await randomizer.shuffle(["foo"]); + + expect(result).toEqual(["foo"]); + expect(result).not.toBe(["foo"]); + expect(cryptoService.randomNumber).not.toHaveBeenCalled(); + }); + + it("shuffles the tail of the list", async () => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + cryptoService.randomNumber.mockResolvedValueOnce(0); + + const result = await randomizer.shuffle(["bar", "foo"]); + + expect(result).toEqual(["foo", "bar"]); + }); + + it("shuffles the list", async () => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + cryptoService.randomNumber.mockResolvedValueOnce(0); + cryptoService.randomNumber.mockResolvedValueOnce(1); + + const result = await randomizer.shuffle(["baz", "bar", "foo"]); + + expect(result).toEqual(["foo", "bar", "baz"]); + }); + + it("returns the input list when options.copy is false", async () => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + cryptoService.randomNumber.mockResolvedValueOnce(0); + + const expectedResult = ["foo"]; + const result = await randomizer.shuffle(expectedResult, { copy: false }); + + expect(result).toBe(expectedResult); + }); + }); + + describe("chars", () => { + it("returns an empty string when the length is 0", async () => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + + const result = await randomizer.chars(0); + + expect(result).toEqual(""); + }); + + it("returns an arbitrary lowercase ascii character", async () => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + cryptoService.randomNumber.mockResolvedValueOnce(0); + + const result = await randomizer.chars(1); + + expect(result).toEqual("a"); + }); + + it("returns a number of ascii characters based on the length", async () => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + cryptoService.randomNumber.mockResolvedValue(0); + + const result = await randomizer.chars(2); + + expect(result).toEqual("aa"); + expect(cryptoService.randomNumber).toHaveBeenCalledTimes(2); + }); + + it("returns a new random character each time its called", async () => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + cryptoService.randomNumber.mockResolvedValueOnce(0); + cryptoService.randomNumber.mockResolvedValueOnce(1); + + const resultA = await randomizer.chars(1); + const resultB = await randomizer.chars(1); + + expect(resultA).toEqual("a"); + expect(resultB).toEqual("b"); + expect(cryptoService.randomNumber).toHaveBeenCalledTimes(2); + }); + }); + + describe("uniform", () => { + it("forwards requests to the crypto service", async () => { + const randomizer = new CryptoServiceRandomizer(cryptoService); + cryptoService.randomNumber.mockResolvedValue(5); + + const result = await randomizer.uniform(0, 5); + + expect(result).toBe(5); + expect(cryptoService.randomNumber).toHaveBeenCalledWith(0, 5); + }); + }); +}); diff --git a/libs/tools/generator/core/src/engine/crypto-service-randomizer.ts b/libs/tools/generator/core/src/engine/crypto-service-randomizer.ts index 5320fad681c..9d3659139c8 100644 --- a/libs/tools/generator/core/src/engine/crypto-service-randomizer.ts +++ b/libs/tools/generator/core/src/engine/crypto-service-randomizer.ts @@ -5,9 +5,17 @@ import { WordOptions } from "../types"; /** A randomizer backed by a CryptoService. */ export class CryptoServiceRandomizer implements Randomizer { + /** instantiates the type. + * @param crypto generates random numbers + */ constructor(private crypto: CryptoService) {} - async pick(list: Array) { + async pick(list: Array): Promise { + const length = list?.length ?? 0; + if (length <= 0) { + throw new Error("list must have at least one entry."); + } + const index = await this.uniform(0, list.length - 1); return list[index]; } @@ -29,6 +37,11 @@ export class CryptoServiceRandomizer implements Randomizer { // ref: https://stackoverflow.com/a/12646864/1090359 async shuffle(items: Array, options?: { copy?: boolean }) { + const length = items?.length ?? 0; + if (length <= 0) { + throw new Error("items must have at least one entry."); + } + const shuffled = options?.copy ?? true ? [...items] : items; for (let i = shuffled.length - 1; i > 0; i--) { @@ -52,11 +65,4 @@ export class CryptoServiceRandomizer implements Randomizer { async uniform(min: number, max: number) { return this.crypto.randomNumber(min, max); } - - // ref: https://stackoverflow.com/a/10073788 - private zeroPad(number: string, width: number) { - return number.length >= width - ? number - : new Array(width - number.length + 1).join("0") + number; - } } diff --git a/libs/tools/generator/core/src/engine/data.ts b/libs/tools/generator/core/src/engine/data.ts new file mode 100644 index 00000000000..d0155a49b25 --- /dev/null +++ b/libs/tools/generator/core/src/engine/data.ts @@ -0,0 +1,35 @@ +import { CharacterSet, CharacterSets } from "./types"; + +function toCharacterSet(characters: string) { + const set = characters.split(""); + + return Object.freeze(set as CharacterSet); +} + +const SpecialCharacters = toCharacterSet("!@#$%^&*"); + +/** Sets of Ascii characters used for password generation */ +export const Ascii = Object.freeze({ + /** The full set of characters available to the generator */ + Full: Object.freeze({ + Uppercase: toCharacterSet("ABCDEFGHIJKLMNOPQRSTUVWXYZ"), + Lowercase: toCharacterSet("abcdefghijkmnopqrstuvwxyz"), + Digit: toCharacterSet("0123456789"), + Special: SpecialCharacters, + } as CharacterSets), + + /** All characters available to the generator that are not ambiguous. */ + Unmistakable: Object.freeze({ + Uppercase: toCharacterSet("ABCDEFGHJKLMNPQRSTUVWXYZ"), + Lowercase: toCharacterSet("abcdefghijklmnopqrstuvwxyz"), + Digit: toCharacterSet("23456789"), + Special: SpecialCharacters, + } as CharacterSets), +}); + +/** Splits an email into a username, subaddress, and domain named group. + * Subaddress is optional. + */ +export const SUBADDRESS_PARSER = new RegExp( + "(?[^@+]+)(?\\+.+)?(?@.+)", +); diff --git a/libs/tools/generator/core/src/engine/email-calculator.spec.ts b/libs/tools/generator/core/src/engine/email-calculator.spec.ts new file mode 100644 index 00000000000..a4aefea11a2 --- /dev/null +++ b/libs/tools/generator/core/src/engine/email-calculator.spec.ts @@ -0,0 +1,69 @@ +import { EmailCalculator } from "./email-calculator"; + +describe("EmailCalculator", () => { + describe("appendToSubaddress", () => { + it.each([[null], [undefined], [""]])( + "returns an empty string when the website is %p", + (website) => { + const calculator = new EmailCalculator(); + + const result = calculator.appendToSubaddress(website, null); + + expect(result).toEqual(""); + }, + ); + + it.each([["noAtSymbol"], ["has spaces"]])( + "returns the unaltered email address when it is invalid (=%p)", + (email) => { + const calculator = new EmailCalculator(); + + const result = calculator.appendToSubaddress("foo", email); + + expect(result).toEqual(email); + }, + ); + + it("creates a subadress part", () => { + const calculator = new EmailCalculator(); + + const result = calculator.appendToSubaddress("baz", "foo@example.com"); + + expect(result).toEqual("foo+baz@example.com"); + }); + + it("appends to a subaddress part", () => { + const calculator = new EmailCalculator(); + + const result = calculator.appendToSubaddress("biz", "foo+bar@example.com"); + + expect(result).toEqual("foo+barbiz@example.com"); + }); + }); + + describe("concatenate", () => { + it.each([[null], [undefined], [""]])("returns null when username is %p", (username) => { + const calculator = new EmailCalculator(); + + const result = calculator.concatenate(username, ""); + + expect(result).toEqual(null); + }); + + it.each([[null], [undefined], [""]])("returns null when domain is %p", (domain) => { + const calculator = new EmailCalculator(); + + const result = calculator.concatenate("foo", domain); + + expect(result).toEqual(null); + }); + + it("appends the username to the domain", () => { + const calculator = new EmailCalculator(); + + const result = calculator.concatenate("foo", "example.com"); + + expect(result).toEqual("foo@example.com"); + }); + }); +}); diff --git a/libs/tools/generator/core/src/engine/email-calculator.ts b/libs/tools/generator/core/src/engine/email-calculator.ts new file mode 100644 index 00000000000..36436e181a1 --- /dev/null +++ b/libs/tools/generator/core/src/engine/email-calculator.ts @@ -0,0 +1,56 @@ +import { SUBADDRESS_PARSER } from "./data"; + +/** Generation algorithms that produce deterministic email addresses */ +export class EmailCalculator { + /** + * Appends appendText to the subaddress of an email address. + * @param appendText The calculation fails if this is shorter than 1 character + * long, undefined, or null. + * @param email the email address to alter. + * @returns `email` with `appendText` added to its subaddress (the part + * following the "+"). If there is no subaddress, a subaddress is created. + * If the email address fails to parse, it is returned unaltered. + */ + appendToSubaddress(appendText: string, email: string) { + let result = (email ?? "").trim(); + + const suffix = (appendText ?? "").trim(); + if (suffix.length < 1) { + return result; + } + + const parsed = SUBADDRESS_PARSER.exec(result); + if (!parsed) { + return result; + } + + const subaddress = (parsed.groups.subaddress ?? "+") + suffix; + result = `${parsed.groups.username}${subaddress}${parsed.groups.domain}`; + + return result; + } + + /** + * Derives an email address from a username and domain name. + * @param username the username part of the email address. The calculation fails if this is + * shorter than 1 character long, undefined, or null. + * @param domain the domain part of the email address. The calculation fails if this is empty, + * undefined, or null. + * @returns an email address or `null` if the calculation fails. + */ + concatenate(username: string, domain: string) { + const emailDomain = domain?.startsWith("@") ? domain.substring(1, Infinity) : domain ?? ""; + if (emailDomain.length < 1) { + return null; + } + + const emailWebsite = username ?? ""; + if (emailWebsite.length < 1) { + return null; + } + + const result = `${emailWebsite}@${emailDomain}`; + + return result; + } +} diff --git a/libs/tools/generator/core/src/engine/email-randomizer.spec.ts b/libs/tools/generator/core/src/engine/email-randomizer.spec.ts new file mode 100644 index 00000000000..8670b8c176f --- /dev/null +++ b/libs/tools/generator/core/src/engine/email-randomizer.spec.ts @@ -0,0 +1,211 @@ +import { mock } from "jest-mock-extended"; + +import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; + +import { Randomizer } from "./abstractions"; +import { EmailRandomizer } from "./email-randomizer"; + +describe("EmailRandomizer", () => { + const randomizer = mock(); + + beforeEach(() => { + randomizer.pickWord.mockResolvedValue("baz"); + + // set to 8 characters since that's the default + randomizer.chars.mockResolvedValue("aaaaaaaa"); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("subaddress", () => { + it("returns an empty string if the generation length is 0", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomAsciiSubaddress("foo@example.com", { length: 0 }); + + expect(result).toEqual("foo@example.com"); + }); + + it("returns an empty string if the generation length is less than 0", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomAsciiSubaddress("foo@example.com", { length: -1 }); + + expect(result).toEqual("foo@example.com"); + }); + + it.each([[null], [undefined], [""]])( + "returns an empty string if the email address is %p", + async (email) => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomAsciiSubaddress(email); + + expect(result).toEqual(""); + }, + ); + + it("returns the input if the email address lacks a username", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomAsciiSubaddress("@example.com"); + + expect(result).toEqual("@example.com"); + }); + + it("returns the input if the email address lacks a domain", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomAsciiSubaddress("foo"); + + expect(result).toEqual("foo"); + }); + + it("generates an email address with a subaddress", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomAsciiSubaddress("foo@example.com"); + + expect(result).toEqual("foo+aaaaaaaa@example.com"); + }); + + it("extends the subaddress when it is provided", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomAsciiSubaddress("foo+bar@example.com"); + + expect(result).toEqual("foo+baraaaaaaaa@example.com"); + }); + + it("defaults to 8 random characters", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + await emailRandomizer.randomAsciiSubaddress("foo@example.com"); + + expect(randomizer.chars).toHaveBeenCalledWith(8); + }); + + it("overrides the default length", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + await emailRandomizer.randomAsciiSubaddress("foo@example.com", { length: 1 }); + + expect(randomizer.chars).toHaveBeenCalledWith(1); + }); + }); + + describe("randomAsciiCatchall", () => { + it.each([[null], [undefined], [""]])("returns null if the domain is %p", async (domain) => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomAsciiCatchall(domain); + + expect(result).toBeNull(); + }); + + it("returns null if the length is 0", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomAsciiCatchall("example.com", { length: 0 }); + + expect(result).toBeNull(); + }); + + it("returns null if the length is less than 0", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomAsciiCatchall("example.com", { length: -1 }); + + expect(result).toBeNull(); + }); + + it("generates a random catchall", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomAsciiCatchall("example.com"); + + expect(result).toEqual("aaaaaaaa@example.com"); + }); + + it("defaults to 8 random characters", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + await emailRandomizer.randomAsciiCatchall("example.com"); + + expect(randomizer.chars).toHaveBeenCalledWith(8); + }); + + it("overrides the default length", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + await emailRandomizer.randomAsciiCatchall("example.com", { length: 1 }); + + expect(randomizer.chars).toHaveBeenCalledWith(1); + }); + }); + + describe("randomWordsCatchall", () => { + it.each([[null], [undefined], [""]])("returns null if the domain is %p", async (domain) => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomWordsCatchall(domain); + + expect(result).toBeNull(); + }); + + it("returns null if the length is 0", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomWordsCatchall("example.com", { numberOfWords: 0 }); + + expect(result).toBeNull(); + }); + + it("returns null if the length is less than 0", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomWordsCatchall("example.com", { + numberOfWords: -1, + }); + + expect(result).toBeNull(); + }); + + it("generates a random word catchall", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const result = await emailRandomizer.randomWordsCatchall("example.com"); + + expect(result).toEqual("baz@example.com"); + }); + + it("defaults to 1 random word", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + await emailRandomizer.randomWordsCatchall("example.com"); + + expect(randomizer.pickWord).toHaveBeenCalledTimes(1); + }); + + it("requests a titleCase word for lengths greater than 1", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + randomizer.pickWord.mockResolvedValueOnce("Biz"); + + await emailRandomizer.randomWordsCatchall("example.com", { numberOfWords: 2 }); + + expect(randomizer.pickWord).toHaveBeenNthCalledWith(1, EFFLongWordList, { titleCase: false }); + expect(randomizer.pickWord).toHaveBeenNthCalledWith(2, EFFLongWordList, { titleCase: true }); + }); + + it("overrides the eff word list", async () => { + const emailRandomizer = new EmailRandomizer(randomizer); + + const expectedWordList = ["some", "arbitrary", "words"]; + await emailRandomizer.randomWordsCatchall("example.com", { words: expectedWordList }); + + expect(randomizer.pickWord).toHaveBeenCalledWith(expectedWordList, { titleCase: false }); + }); + }); +}); diff --git a/libs/tools/generator/core/src/engine/email-randomizer.ts b/libs/tools/generator/core/src/engine/email-randomizer.ts new file mode 100644 index 00000000000..7ea5c7e6078 --- /dev/null +++ b/libs/tools/generator/core/src/engine/email-randomizer.ts @@ -0,0 +1,99 @@ +import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; + +import { Randomizer } from "./abstractions"; +import { SUBADDRESS_PARSER } from "./data"; + +/** Generation algorithms that produce randomized email addresses */ +export class EmailRandomizer { + /** Instantiates the email randomizer + * @param random data source for random data + */ + constructor(private random: Randomizer) {} + + /** Appends a random set of characters as a subaddress + * @param email the email address used to generate a subaddress. If this address + * already contains a subaddress, the subaddress is extended. + * @param options.length the number of characters to append to the subaddress. Defaults to 8. If + * the length is <= 0, the function returns the input address. + * @returns a promise that resolves with the generated email address. If the provided address + * lacks a username (the part before the "@") or domain (the part after the "@"), the function + * returns the input address. + */ + async randomAsciiSubaddress(email: string, options?: { length?: number }) { + let result = email ?? ""; + + const subaddressLength = options?.length ?? 8; + if (subaddressLength < 1) { + return result; + } + + const parsed = SUBADDRESS_PARSER.exec(result); + if (!parsed) { + return result; + } + + let subaddress = parsed.groups.subaddress ?? "+"; + subaddress += await this.random.chars(subaddressLength); + result = `${parsed.groups.username}${subaddress}${parsed.groups.domain}`; + + return result; + } + + /** Creates a catchall address composed of random characters + * @param domain the domain part of the generated email address. + * @param options.length the number of characters to include in the catchall + * address. Defaults to 8. + * @returns a promise that resolves with the generated email address. If the domain + * is empty, resolves to null instead. + */ + async randomAsciiCatchall(domain: string, options?: { length?: number }) { + const emailDomain = domain?.startsWith("@") ? domain.substring(1, Infinity) : domain ?? ""; + if (emailDomain.length < 1) { + return null; + } + + const length = options?.length ?? 8; + if (length < 1) { + return null; + } + + const catchall = await this.random.chars(length); + const result = `${catchall}@${domain}`; + + return result; + } + + /** Creates a catchall address composed of random words + * @param domain the domain part of the generated email address. + * @param options.numberOfWords the number of words to include in the catchall + * address. Defaults to 1. + * @param options.words selects words from the provided wordlist. Defaults to + * the EFF "5-dice" list. + * @returns a promise that resolves with the generated email address. + */ + async randomWordsCatchall( + domain: string, + options?: { numberOfWords?: number; words?: Array }, + ) { + const emailDomain = domain?.startsWith("@") ? domain.substring(1, Infinity) : domain ?? ""; + if (emailDomain.length < 1) { + return null; + } + + const numberOfWords = options?.numberOfWords ?? 1; + if (numberOfWords < 1) { + return null; + } + + const wordList = options?.words ?? EFFLongWordList; + const words = []; + for (let i = 0; i < numberOfWords; i++) { + // camelCase the words for legibility + words[i] = await this.random.pickWord(wordList, { titleCase: i !== 0 }); + } + + const result = `${words.join("")}@${domain}`; + + return result; + } +} diff --git a/libs/tools/generator/core/src/engine/index.ts b/libs/tools/generator/core/src/engine/index.ts index 1a67384de1a..38e3195b966 100644 --- a/libs/tools/generator/core/src/engine/index.ts +++ b/libs/tools/generator/core/src/engine/index.ts @@ -1 +1,5 @@ export { CryptoServiceRandomizer } from "./crypto-service-randomizer"; +export { EmailRandomizer } from "./email-randomizer"; +export { EmailCalculator } from "./email-calculator"; +export { PasswordRandomizer } from "./password-randomizer"; +export { UsernameRandomizer } from "./username-randomizer"; diff --git a/libs/tools/generator/core/src/engine/password-randomizer.spec.ts b/libs/tools/generator/core/src/engine/password-randomizer.spec.ts new file mode 100644 index 00000000000..bbc31a4a293 --- /dev/null +++ b/libs/tools/generator/core/src/engine/password-randomizer.spec.ts @@ -0,0 +1,338 @@ +import { mock } from "jest-mock-extended"; + +import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; + +import { Randomizer } from "../abstractions"; + +import { Ascii } from "./data"; +import { PasswordRandomizer } from "./password-randomizer"; +import { CharacterSet, RandomAsciiRequest } from "./types"; + +describe("PasswordRandomizer", () => { + const randomizer = mock(); + + beforeEach(() => { + randomizer.shuffle.mockImplementation((items) => { + return Promise.resolve([...items]); + }); + + randomizer.pick.mockImplementation((items) => { + return Promise.resolve(items[0]); + }); + + randomizer.pickWord.mockResolvedValue("foo"); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("randomAscii", () => { + it("returns the empty string when no character sets are specified", async () => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomAscii({ + all: 1, + ambiguous: true, + }); + + expect(result).toEqual(""); + }); + + it("generates an uppercase ascii password", async () => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomAscii({ + all: 0, + uppercase: 1, + ambiguous: true, + }); + + expect(result).toEqual("A"); + expect(randomizer.pick).toHaveBeenCalledWith(Ascii.Full.Uppercase); + }); + + it("generates an uppercase ascii password without ambiguous characters", async () => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomAscii({ + all: 0, + uppercase: 1, + ambiguous: false, + }); + + expect(result).toEqual("A"); + expect(randomizer.pick).toHaveBeenCalledWith(Ascii.Unmistakable.Uppercase); + }); + + it("generates a lowercase ascii password", async () => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomAscii({ + all: 0, + lowercase: 1, + ambiguous: true, + }); + + expect(result).toEqual("a"); + expect(randomizer.pick).toHaveBeenCalledWith(Ascii.Full.Lowercase); + }); + + it("generates a lowercase ascii password without ambiguous characters", async () => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomAscii({ + all: 0, + lowercase: 1, + ambiguous: false, + }); + + expect(result).toEqual("a"); + expect(randomizer.pick).toHaveBeenCalledWith(Ascii.Unmistakable.Lowercase); + }); + + it("generates a numeric ascii password", async () => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomAscii({ + all: 0, + digits: 1, + ambiguous: true, + }); + + expect(result).toEqual("0"); + expect(randomizer.pick).toHaveBeenCalledWith(Ascii.Full.Digit); + }); + + it("generates a numeric password without ambiguous characters", async () => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomAscii({ + all: 0, + digits: 1, + ambiguous: false, + }); + + expect(result).toEqual("2"); + expect(randomizer.pick).toHaveBeenCalledWith(Ascii.Unmistakable.Digit); + }); + + it("generates a special character password", async () => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomAscii({ + all: 0, + special: 1, + ambiguous: true, + }); + + expect(result).toEqual("!"); + expect(randomizer.pick).toHaveBeenCalledWith(Ascii.Full.Special); + }); + + it("generates a special character password without ambiguous characters", async () => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomAscii({ + all: 0, + special: 1, + ambiguous: false, + }); + + expect(result).toEqual("!"); + expect(randomizer.pick).toHaveBeenCalledWith(Ascii.Unmistakable.Special); + }); + + it.each([ + [2, "AA"], + [3, "AAA"], + ])("includes %p uppercase characters", async (uppercase, expected) => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomAscii({ + all: 0, + uppercase, + ambiguous: true, + }); + + expect(result).toEqual(expected); + }); + + it.each([ + [2, "aa"], + [3, "aaa"], + ])("includes %p lowercase characters", async (lowercase, expected) => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomAscii({ + all: 0, + lowercase, + ambiguous: true, + }); + + expect(result).toEqual(expected); + }); + + it.each([ + [2, "00"], + [3, "000"], + ])("includes %p digits", async (digits, expected) => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomAscii({ + all: 0, + digits, + ambiguous: true, + }); + + expect(result).toEqual(expected); + }); + + it.each([ + [2, "!!"], + [3, "!!!"], + ])("includes %p special characters", async (special, expected) => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomAscii({ + all: 0, + special, + ambiguous: true, + }); + + expect(result).toEqual(expected); + }); + + it.each([ + [{ uppercase: 0 }, Ascii.Full.Uppercase], + [{ lowercase: 0 }, Ascii.Full.Lowercase], + [{ digits: 0 }, Ascii.Full.Digit], + [{ special: 0 }, Ascii.Full.Special], + ])( + "mixes character sets for the remaining characters (=%p)", + async (setting: Partial, set: CharacterSet) => { + const password = new PasswordRandomizer(randomizer); + + await password.randomAscii({ + ...setting, + all: 1, + ambiguous: true, + }); + + expect(randomizer.pick).toHaveBeenCalledWith(set); + }, + ); + + it("shuffles the password characters", async () => { + const password = new PasswordRandomizer(randomizer); + + // Typically `shuffle` randomizes the order of the array it's been + // given. In the password generator, the array is generated from the + // options. Thus, returning a fixed set of results effectively overrides + // the randomizer's arguments. + randomizer.shuffle.mockImplementation(() => { + const results = [Ascii.Full.Uppercase, Ascii.Full.Digit]; + return Promise.resolve(results); + }); + + const result = await password.randomAscii({ + all: 0, + ambiguous: true, + }); + + expect(result).toEqual("A0"); + }); + }); + + describe("randomEffLongWords", () => { + it("generates the empty string when no words are passed", async () => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomEffLongWords({ + numberOfWords: 0, + separator: "", + number: false, + capitalize: false, + }); + + expect(result).toEqual(""); + }); + + it.each([ + [1, "foo"], + [2, "foofoo"], + ])("generates a %i-length word list", async (words, expected) => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomEffLongWords({ + numberOfWords: words, + separator: "", + number: false, + capitalize: false, + }); + + expect(result).toEqual(expected); + expect(randomizer.pickWord).toHaveBeenCalledWith(EFFLongWordList, { + titleCase: false, + number: false, + }); + }); + + it("capitalizes the word list", async () => { + const password = new PasswordRandomizer(randomizer); + randomizer.pickWord.mockResolvedValueOnce("Foo"); + + const result = await password.randomEffLongWords({ + numberOfWords: 1, + separator: "", + number: false, + capitalize: true, + }); + + expect(result).toEqual("Foo"); + expect(randomizer.pickWord).toHaveBeenCalledWith(EFFLongWordList, { + titleCase: true, + number: false, + }); + }); + + it("includes a random number on a random word", async () => { + const password = new PasswordRandomizer(randomizer); + randomizer.pickWord.mockResolvedValueOnce("foo"); + randomizer.pickWord.mockResolvedValueOnce("foo1"); + + // chooses which word gets the number + randomizer.uniform.mockResolvedValueOnce(1); + + const result = await password.randomEffLongWords({ + numberOfWords: 2, + separator: "", + number: true, + capitalize: false, + }); + + expect(result).toEqual("foofoo1"); + expect(randomizer.pickWord).toHaveBeenNthCalledWith(1, EFFLongWordList, { + titleCase: false, + number: false, + }); + expect(randomizer.pickWord).toHaveBeenNthCalledWith(2, EFFLongWordList, { + titleCase: false, + number: true, + }); + }); + + it("includes a separator", async () => { + const password = new PasswordRandomizer(randomizer); + + const result = await password.randomEffLongWords({ + numberOfWords: 2, + separator: "-", + number: false, + capitalize: false, + }); + + expect(result).toEqual("foo-foo"); + }); + }); +}); diff --git a/libs/tools/generator/core/src/engine/password-randomizer.ts b/libs/tools/generator/core/src/engine/password-randomizer.ts new file mode 100644 index 00000000000..438ea8b8b47 --- /dev/null +++ b/libs/tools/generator/core/src/engine/password-randomizer.ts @@ -0,0 +1,95 @@ +import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; + +import { Randomizer } from "./abstractions"; +import { Ascii } from "./data"; +import { CharacterSet, EffWordListRequest, RandomAsciiRequest } from "./types"; + +/** Generation algorithms that produce randomized secrets */ +export class PasswordRandomizer { + /** Instantiates the password randomizer + * @param random data source for random data + */ + constructor(private randomizer: Randomizer) {} + + /** create a password from ASCII codepoints + * @param request refines the generated password + * @returns a promise that completes with the generated password + */ + async randomAscii(request: RandomAsciiRequest) { + // randomize character sets + const sets = toAsciiSets(request); + const shuffled = await this.randomizer.shuffle(sets); + + // generate password + const generating = shuffled.flatMap((set) => this.randomizer.pick(set)); + const generated = await Promise.all(generating); + const result = generated.join(""); + + return result; + } + + /** create a passphrase from the EFF's "5 dice" word list + * @param request refines the generated passphrase + * @returns a promise that completes with the generated passphrase + */ + async randomEffLongWords(request: EffWordListRequest) { + // select which word gets the number, if any + let luckyNumber = -1; + if (request.number) { + luckyNumber = await this.randomizer.uniform(0, request.numberOfWords - 1); + } + + // generate the passphrase + const wordList = new Array(request.numberOfWords); + for (let i = 0; i < request.numberOfWords; i++) { + const word = await this.randomizer.pickWord(EFFLongWordList, { + titleCase: request.capitalize, + number: i === luckyNumber, + }); + + wordList[i] = word; + } + + return wordList.join(request.separator); + } +} + +// given a generator request, convert each of its `number | undefined` properties +// to an array of character sets, one for each property. The transformation is +// deterministic. +function toAsciiSets(request: RandomAsciiRequest) { + // allocate an array and initialize each cell with a fixed value + function allocate(size: number, value: T) { + const data = new Array(size > 0 ? size : 0); + data.fill(value, 0, size); + return data; + } + + const allSet: CharacterSet = []; + const active = request.ambiguous ? Ascii.Full : Ascii.Unmistakable; + const parts: Array = []; + + if (request.uppercase !== undefined) { + parts.push(...allocate(request.uppercase, active.Uppercase)); + allSet.push(...active.Uppercase); + } + + if (request.lowercase !== undefined) { + parts.push(...allocate(request.lowercase, active.Lowercase)); + allSet.push(...active.Lowercase); + } + + if (request.digits !== undefined) { + parts.push(...allocate(request.digits, active.Digit)); + allSet.push(...active.Digit); + } + + if (request.special !== undefined) { + parts.push(...allocate(request.special, active.Special)); + allSet.push(...active.Special); + } + + parts.push(...allocate(request.all, allSet)); + + return parts; +} diff --git a/libs/tools/generator/core/src/engine/types.ts b/libs/tools/generator/core/src/engine/types.ts new file mode 100644 index 00000000000..d9eee055922 --- /dev/null +++ b/libs/tools/generator/core/src/engine/types.ts @@ -0,0 +1,68 @@ +/** Each entry of a character set contains a codepoint used for password generation */ +export type CharacterSet = string[]; + +/** Well known character sets used for password generation */ +export type CharacterSets = { + /** A set of uppercase characters */ + Uppercase: CharacterSet; + + /** A set of lowercase characters */ + Lowercase: CharacterSet; + + /** A set of numeric characters (i.e., digits) */ + Digit: CharacterSet; + + /** A set of special characters (e.g. "$") */ + Special: CharacterSet; +}; + +/** Request a random password using ascii characters */ +export type RandomAsciiRequest = { + /** Number of codepoints drawn from all available character sets */ + all: number; + + /** Number of codepoints drawn from uppercase character sets */ + uppercase?: number; + + /** Number of codepoints drawn from lowercase character sets */ + lowercase?: number; + + /** Number of codepoints drawn from numeric character sets */ + digits?: number; + + /** Number of codepoints drawn from special character sets */ + special?: number; + + /** When `false`, characters with ambiguous glyphs (e.g., "I", "l", and "1") are excluded from the generated password. */ + ambiguous: boolean; +}; + +/** Request random words drawn from the EFF "5 dice" word list */ +export type EffWordListRequest = { + /** Number of words drawn from the word list */ + numberOfWords: number; + + /** Separator rendered in between each word */ + separator: string; + + /** Whether or not a word should include a random digit */ + number: boolean; + + /** Whether or not the words should be capitalized */ + capitalize: boolean; +}; + +/** request random username drawn from a word list */ +export type WordsRequest = { + /** the number of words to select. This defaults to 1. */ + numberOfWords?: number; + + /** Draw the words from a custom word list; defaults to the EFF "5 dice" word list. */ + words?: Array; + + /** The number of digits to append to the random word(s). Defaults to 0. */ + digits?: number; + + /** Expected casing of the returned words. Defaults to lowercase. */ + casing?: "lowercase" | "TitleCase" | "camelCase"; +}; diff --git a/libs/tools/generator/core/src/engine/username-randomizer.spec.ts b/libs/tools/generator/core/src/engine/username-randomizer.spec.ts new file mode 100644 index 00000000000..e30db286452 --- /dev/null +++ b/libs/tools/generator/core/src/engine/username-randomizer.spec.ts @@ -0,0 +1,105 @@ +import { mock } from "jest-mock-extended"; + +import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; + +import { Randomizer } from "./abstractions"; +import { UsernameRandomizer } from "./username-randomizer"; + +describe("UsernameRandomizer", () => { + const randomizer = mock(); + + beforeEach(() => { + randomizer.pickWord.mockResolvedValue("username"); + randomizer.uniform.mockResolvedValue(0); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("randomWords", () => { + it("generates a random word", async () => { + const usernameRandomizer = new UsernameRandomizer(randomizer); + + const result = await usernameRandomizer.randomWords(); + + expect(result).toEqual("username"); + }); + + it("generates multiple random words", async () => { + const usernameRandomizer = new UsernameRandomizer(randomizer); + + const result = await usernameRandomizer.randomWords({ numberOfWords: 2 }); + + expect(result).toEqual("usernameusername"); + }); + + it("returns an empty string if length is 0", async () => { + const usernameRandomizer = new UsernameRandomizer(randomizer); + + const result = await usernameRandomizer.randomWords({ numberOfWords: 0 }); + + expect(result).toEqual(""); + }); + + it("returns an empty string if length is less than 0", async () => { + const usernameRandomizer = new UsernameRandomizer(randomizer); + + const result = await usernameRandomizer.randomWords({ numberOfWords: -1 }); + + expect(result).toEqual(""); + }); + + it("selects from a custom wordlist", async () => { + const usernameRandomizer = new UsernameRandomizer(randomizer); + + const expectedWords: string[] = []; + const result = await usernameRandomizer.randomWords({ + numberOfWords: 1, + words: expectedWords, + }); + + expect(result).toEqual("username"); + expect(randomizer.pickWord).toHaveBeenCalledWith(expectedWords, { titleCase: false }); + }); + + it("camelCases words", async () => { + const usernameRandomizer = new UsernameRandomizer(randomizer); + + const result = await usernameRandomizer.randomWords({ + numberOfWords: 2, + casing: "camelCase", + }); + + expect(result).toEqual("usernameusername"); + expect(randomizer.pickWord).toHaveBeenNthCalledWith(1, EFFLongWordList, { titleCase: false }); + expect(randomizer.pickWord).toHaveBeenNthCalledWith(2, EFFLongWordList, { titleCase: true }); + }); + + it("TitleCasesWords", async () => { + const usernameRandomizer = new UsernameRandomizer(randomizer); + + const result = await usernameRandomizer.randomWords({ + numberOfWords: 2, + casing: "TitleCase", + }); + + expect(result).toEqual("usernameusername"); + expect(randomizer.pickWord).toHaveBeenNthCalledWith(1, EFFLongWordList, { titleCase: true }); + expect(randomizer.pickWord).toHaveBeenNthCalledWith(2, EFFLongWordList, { titleCase: true }); + }); + + it("lowercases words", async () => { + const usernameRandomizer = new UsernameRandomizer(randomizer); + + const result = await usernameRandomizer.randomWords({ + numberOfWords: 2, + casing: "lowercase", + }); + + expect(result).toEqual("usernameusername"); + expect(randomizer.pickWord).toHaveBeenNthCalledWith(1, EFFLongWordList, { titleCase: false }); + expect(randomizer.pickWord).toHaveBeenNthCalledWith(2, EFFLongWordList, { titleCase: false }); + }); + }); +}); diff --git a/libs/tools/generator/core/src/engine/username-randomizer.ts b/libs/tools/generator/core/src/engine/username-randomizer.ts new file mode 100644 index 00000000000..4a6aa43f604 --- /dev/null +++ b/libs/tools/generator/core/src/engine/username-randomizer.ts @@ -0,0 +1,47 @@ +import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; + +import { Randomizer } from "./abstractions"; +import { WordsRequest } from "./types"; + +/** Generation algorithms that produce randomized usernames */ +export class UsernameRandomizer { + /** Instantiates the username randomizer + * @param random data source for random data + */ + constructor(private random: Randomizer) {} + + /** Creates a username composed of random words + * @param request parameters to which the generated username conforms + * @returns a promise that resolves with the generated username. + */ + async randomWords(request?: WordsRequest) { + const numberOfWords = request?.numberOfWords ?? 1; + if (numberOfWords < 1) { + return ""; + } + + const digits = Math.max(request?.digits ?? 0, 0); + let selectCase = (_: number) => false; + if (request?.casing === "camelCase") { + selectCase = (i: number) => i !== 0; + } else if (request?.casing === "TitleCase") { + selectCase = (_: number) => true; + } + + const wordList = request?.words ?? EFFLongWordList; + const parts = []; + for (let i = 0; i < numberOfWords; i++) { + const word = await this.random.pickWord(wordList, { titleCase: selectCase(i) }); + parts.push(word); + } + + for (let i = 0; i < digits; i++) { + const digit = await this.random.uniform(0, 9); + parts.push(digit.toString()); + } + + const result = parts.join(""); + + return result; + } +} diff --git a/libs/tools/generator/core/src/strategies/catchall-generator-strategy.spec.ts b/libs/tools/generator/core/src/strategies/catchall-generator-strategy.spec.ts index dcb7227b1c3..99f618a4520 100644 --- a/libs/tools/generator/core/src/strategies/catchall-generator-strategy.spec.ts +++ b/libs/tools/generator/core/src/strategies/catchall-generator-strategy.spec.ts @@ -8,8 +8,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { StateProvider } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; -import { Randomizer } from "../abstractions"; import { DefaultCatchallOptions } from "../data"; +import { EmailCalculator, EmailRandomizer } from "../engine"; import { DefaultPolicyEvaluator } from "../policies"; import { CatchallGeneratorStrategy } from "./catchall-generator-strategy"; @@ -28,7 +28,7 @@ describe("Email subaddress list generation strategy", () => { it.each([[[]], [null], [undefined], [[SomePolicy]], [[SomePolicy, SomePolicy]]])( "should map any input (= %p) to the default policy evaluator", async (policies) => { - const strategy = new CatchallGeneratorStrategy(null, null); + const strategy = new CatchallGeneratorStrategy(null, null, null); const evaluator$ = of(policies).pipe(strategy.toEvaluator()); const evaluator = await firstValueFrom(evaluator$); @@ -41,8 +41,7 @@ describe("Email subaddress list generation strategy", () => { describe("durableState", () => { it("should use password settings key", () => { const provider = mock(); - const randomizer = mock(); - const strategy = new CatchallGeneratorStrategy(randomizer, provider); + const strategy = new CatchallGeneratorStrategy(null, null, provider); strategy.durableState(SomeUser); @@ -52,7 +51,7 @@ describe("Email subaddress list generation strategy", () => { describe("defaults$", () => { it("should return the default subaddress options", async () => { - const strategy = new CatchallGeneratorStrategy(null, null); + const strategy = new CatchallGeneratorStrategy(null, null, null); const result = await firstValueFrom(strategy.defaults$(SomeUser)); @@ -62,14 +61,52 @@ describe("Email subaddress list generation strategy", () => { describe("policy", () => { it("should use password generator policy", () => { - const randomizer = mock(); - const strategy = new CatchallGeneratorStrategy(randomizer, null); + const strategy = new CatchallGeneratorStrategy(null, null, null); expect(strategy.policy).toBe(PolicyType.PasswordGenerator); }); }); describe("generate()", () => { - it.todo("generate catchall email addresses"); + it("generates a random catchall by default", async () => { + const randomizer = mock(); + randomizer.randomAsciiCatchall.mockResolvedValue("catchall@example.com"); + const strategy = new CatchallGeneratorStrategy(null, randomizer, null); + + const result = await strategy.generate({ catchallDomain: "example.com", website: "" }); + + expect(result).toEqual("catchall@example.com"); + expect(randomizer.randomAsciiCatchall).toHaveBeenCalledWith("example.com"); + }); + + it("generates random catchall email addresses", async () => { + const randomizer = mock(); + randomizer.randomAsciiCatchall.mockResolvedValue("catchall@example.com"); + const strategy = new CatchallGeneratorStrategy(null, randomizer, null); + + const result = await strategy.generate({ + catchallType: "random", + catchallDomain: "example.com", + website: "", + }); + + expect(result).toEqual("catchall@example.com"); + expect(randomizer.randomAsciiCatchall).toHaveBeenCalledWith("example.com"); + }); + + it("generates catchall email addresses from website", async () => { + const calculator = mock(); + calculator.concatenate.mockReturnValue("catchall@example.com"); + const strategy = new CatchallGeneratorStrategy(calculator, null, null); + + const result = await strategy.generate({ + catchallType: "website-name", + catchallDomain: "example.com", + website: "foo.com", + }); + + expect(result).toEqual("catchall@example.com"); + expect(calculator.concatenate).toHaveBeenCalledWith("foo.com", "example.com"); + }); }); }); diff --git a/libs/tools/generator/core/src/strategies/catchall-generator-strategy.ts b/libs/tools/generator/core/src/strategies/catchall-generator-strategy.ts index af7e2b61f4f..53769dfa308 100644 --- a/libs/tools/generator/core/src/strategies/catchall-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/catchall-generator-strategy.ts @@ -1,8 +1,9 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { StateProvider } from "@bitwarden/common/platform/state"; -import { GeneratorStrategy, Randomizer } from "../abstractions"; +import { GeneratorStrategy } from "../abstractions"; import { DefaultCatchallOptions } from "../data"; +import { EmailCalculator, EmailRandomizer } from "../engine"; import { newDefaultEvaluator } from "../rx"; import { NoPolicy, CatchallGenerationOptions } from "../types"; import { clone$PerUserId, sharedStateByUserId } from "../util"; @@ -17,7 +18,8 @@ export class CatchallGeneratorStrategy * @param usernameService generates a catchall address for a domain */ constructor( - private random: Randomizer, + private emailCalculator: EmailCalculator, + private emailRandomizer: EmailRandomizer, private stateProvider: StateProvider, private defaultOptions: CatchallGenerationOptions = DefaultCatchallOptions, ) {} @@ -30,21 +32,14 @@ export class CatchallGeneratorStrategy // algorithm async generate(options: CatchallGenerationOptions) { - const o = Object.assign({}, DefaultCatchallOptions, options); - - if (o.catchallDomain == null || o.catchallDomain === "") { - return null; - } - if (o.catchallType == null) { - o.catchallType = "random"; + if (options.catchallType == null) { + options.catchallType = "random"; } - let startString = ""; - if (o.catchallType === "random") { - startString = await this.random.chars(8); - } else if (o.catchallType === "website-name") { - startString = o.website; + if (options.catchallType === "website-name") { + return await this.emailCalculator.concatenate(options.website, options.catchallDomain); } - return startString + "@" + o.catchallDomain; + + return this.emailRandomizer.randomAsciiCatchall(options.catchallDomain); } } diff --git a/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.spec.ts b/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.spec.ts index 85836647319..d3582127ade 100644 --- a/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.spec.ts +++ b/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.spec.ts @@ -8,8 +8,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { StateProvider } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; -import { Randomizer } from "../abstractions"; import { DefaultEffUsernameOptions } from "../data"; +import { UsernameRandomizer } from "../engine"; import { DefaultPolicyEvaluator } from "../policies"; import { EffUsernameGeneratorStrategy } from "./eff-username-generator-strategy"; @@ -41,8 +41,7 @@ describe("EFF long word list generation strategy", () => { describe("durableState", () => { it("should use password settings key", () => { const provider = mock(); - const randomizer = mock(); - const strategy = new EffUsernameGeneratorStrategy(randomizer, provider); + const strategy = new EffUsernameGeneratorStrategy(null, provider); strategy.durableState(SomeUser); @@ -62,14 +61,104 @@ describe("EFF long word list generation strategy", () => { describe("policy", () => { it("should use password generator policy", () => { - const randomizer = mock(); - const strategy = new EffUsernameGeneratorStrategy(randomizer, null); + const strategy = new EffUsernameGeneratorStrategy(null, null); expect(strategy.policy).toBe(PolicyType.PasswordGenerator); }); }); describe("generate()", () => { - it.todo("generate username tests"); + const randomizer = mock(); + + beforeEach(() => { + randomizer.randomWords.mockResolvedValue("username"); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it("generates a username", async () => { + const strategy = new EffUsernameGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + wordCapitalize: false, + wordIncludeNumber: false, + website: null, + }); + + expect(result).toEqual("username"); + expect(randomizer.randomWords).toHaveBeenCalledWith({ + numberOfWords: 1, + casing: "lowercase", + digits: 0, + }); + }); + + it("includes a 4-digit number in the username", async () => { + const strategy = new EffUsernameGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + wordCapitalize: false, + wordIncludeNumber: true, + website: null, + }); + + expect(result).toEqual("username"); + expect(randomizer.randomWords).toHaveBeenCalledWith({ + numberOfWords: 1, + casing: "lowercase", + digits: 4, + }); + }); + + it("capitalizes the username", async () => { + const strategy = new EffUsernameGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + wordCapitalize: true, + wordIncludeNumber: false, + website: null, + }); + + expect(result).toEqual("username"); + expect(randomizer.randomWords).toHaveBeenCalledWith({ + numberOfWords: 1, + casing: "TitleCase", + digits: 0, + }); + }); + + it("defaults to lowercase", async () => { + const strategy = new EffUsernameGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + wordIncludeNumber: false, + website: null, + }); + + expect(result).toEqual("username"); + expect(randomizer.randomWords).toHaveBeenCalledWith({ + numberOfWords: 1, + casing: "lowercase", + digits: 0, + }); + }); + + it("defaults to a word without digits", async () => { + const strategy = new EffUsernameGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + wordCapitalize: false, + website: null, + }); + + expect(result).toEqual("username"); + expect(randomizer.randomWords).toHaveBeenCalledWith({ + numberOfWords: 1, + casing: "lowercase", + digits: 0, + }); + }); }); }); diff --git a/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts b/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts index bcedfb60a71..cd802b5a5ba 100644 --- a/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts @@ -1,9 +1,9 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; import { StateProvider } from "@bitwarden/common/platform/state"; -import { GeneratorStrategy, Randomizer } from "../abstractions"; -import { DefaultEffUsernameOptions } from "../data"; +import { GeneratorStrategy } from "../abstractions"; +import { DefaultEffUsernameOptions, UsernameDigits } from "../data"; +import { UsernameRandomizer } from "../engine"; import { newDefaultEvaluator } from "../rx"; import { EffUsernameGenerationOptions, NoPolicy } from "../types"; import { clone$PerUserId, sharedStateByUserId } from "../util"; @@ -18,7 +18,7 @@ export class EffUsernameGeneratorStrategy * @param usernameService generates a username from EFF word list */ constructor( - private random: Randomizer, + private randomizer: UsernameRandomizer, private stateProvider: StateProvider, private defaultOptions: EffUsernameGenerationOptions = DefaultEffUsernameOptions, ) {} @@ -31,10 +31,15 @@ export class EffUsernameGeneratorStrategy // algorithm async generate(options: EffUsernameGenerationOptions) { - const word = await this.random.pickWord(EFFLongWordList, { - titleCase: options.wordCapitalize ?? DefaultEffUsernameOptions.wordCapitalize, - number: options.wordIncludeNumber ?? DefaultEffUsernameOptions.wordIncludeNumber, - }); + const casing = + options.wordCapitalize ?? DefaultEffUsernameOptions.wordCapitalize + ? "TitleCase" + : "lowercase"; + const digits = + options.wordIncludeNumber ?? DefaultEffUsernameOptions.wordIncludeNumber + ? UsernameDigits.enabled + : UsernameDigits.disabled; + const word = await this.randomizer.randomWords({ numberOfWords: 1, casing, digits }); return word; } } diff --git a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts index 3620fd76f20..f3a6046facf 100644 --- a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts +++ b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts @@ -8,8 +8,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { StateProvider } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; -import { Randomizer } from "../abstractions"; import { DefaultPassphraseGenerationOptions, DisabledPassphraseGeneratorPolicy } from "../data"; +import { PasswordRandomizer } from "../engine"; import { PassphraseGeneratorOptionsEvaluator } from "../policies"; import { PassphraseGeneratorStrategy } from "./passphrase-generator-strategy"; @@ -58,8 +58,7 @@ describe("Password generation strategy", () => { describe("durableState", () => { it("should use password settings key", () => { const provider = mock(); - const randomizer = mock(); - const strategy = new PassphraseGeneratorStrategy(randomizer, provider); + const strategy = new PassphraseGeneratorStrategy(null, provider); strategy.durableState(SomeUser); @@ -79,14 +78,111 @@ describe("Password generation strategy", () => { describe("policy", () => { it("should use password generator policy", () => { - const randomizer = mock(); - const strategy = new PassphraseGeneratorStrategy(randomizer, null); + const strategy = new PassphraseGeneratorStrategy(null, null); expect(strategy.policy).toBe(PolicyType.PasswordGenerator); }); }); describe("generate()", () => { - it.todo("should generate a password using the given options"); + const randomizer = mock(); + beforeEach(() => { + randomizer.randomEffLongWords.mockResolvedValue("passphrase"); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it("should map options", async () => { + const strategy = new PassphraseGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + numWords: 4, + capitalize: true, + includeNumber: true, + wordSeparator: "!", + }); + + expect(result).toEqual("passphrase"); + expect(randomizer.randomEffLongWords).toHaveBeenCalledWith({ + numberOfWords: 4, + capitalize: true, + number: true, + separator: "!", + }); + }); + + it("should default numWords", async () => { + const strategy = new PassphraseGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + capitalize: true, + includeNumber: true, + wordSeparator: "!", + }); + + expect(result).toEqual("passphrase"); + expect(randomizer.randomEffLongWords).toHaveBeenCalledWith({ + numberOfWords: DefaultPassphraseGenerationOptions.numWords, + capitalize: true, + number: true, + separator: "!", + }); + }); + + it("should default capitalize", async () => { + const strategy = new PassphraseGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + numWords: 4, + includeNumber: true, + wordSeparator: "!", + }); + + expect(result).toEqual("passphrase"); + expect(randomizer.randomEffLongWords).toHaveBeenCalledWith({ + numberOfWords: 4, + capitalize: DefaultPassphraseGenerationOptions.capitalize, + number: true, + separator: "!", + }); + }); + + it("should default includeNumber", async () => { + const strategy = new PassphraseGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + numWords: 4, + capitalize: true, + wordSeparator: "!", + }); + + expect(result).toEqual("passphrase"); + expect(randomizer.randomEffLongWords).toHaveBeenCalledWith({ + numberOfWords: 4, + capitalize: true, + number: DefaultPassphraseGenerationOptions.includeNumber, + separator: "!", + }); + }); + + it("should default wordSeparator", async () => { + const strategy = new PassphraseGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + numWords: 4, + capitalize: true, + includeNumber: true, + }); + + expect(result).toEqual("passphrase"); + expect(randomizer.randomEffLongWords).toHaveBeenCalledWith({ + numberOfWords: 4, + capitalize: true, + number: true, + separator: DefaultPassphraseGenerationOptions.wordSeparator, + }); + }); }); }); diff --git a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts index 7fdadaf8e24..78d7ed42a84 100644 --- a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts @@ -1,9 +1,9 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; import { StateProvider } from "@bitwarden/common/platform/state"; -import { GeneratorStrategy, Randomizer } from "../abstractions"; -import { DefaultPassphraseGenerationOptions, Policies } from "../data"; +import { GeneratorStrategy } from "../abstractions"; +import { DefaultPassphraseBoundaries, DefaultPassphraseGenerationOptions, Policies } from "../data"; +import { PasswordRandomizer } from "../engine"; import { mapPolicyToEvaluator } from "../rx"; import { PassphraseGenerationOptions, PassphraseGeneratorPolicy } from "../types"; import { clone$PerUserId, sharedStateByUserId } from "../util"; @@ -19,7 +19,7 @@ export class PassphraseGeneratorStrategy * @param stateProvider provides durable state */ constructor( - private randomizer: Randomizer, + private randomizer: PasswordRandomizer, private stateProvider: StateProvider, ) {} @@ -33,34 +33,14 @@ export class PassphraseGeneratorStrategy // algorithm async generate(options: PassphraseGenerationOptions): Promise { - const o = { ...DefaultPassphraseGenerationOptions, ...options }; - if (o.numWords == null || o.numWords <= 2) { - o.numWords = DefaultPassphraseGenerationOptions.numWords; - } - if (o.capitalize == null) { - o.capitalize = false; - } - if (o.includeNumber == null) { - o.includeNumber = false; - } + const requestWords = options.numWords ?? DefaultPassphraseGenerationOptions.numWords; + const request = { + numberOfWords: Math.max(requestWords, DefaultPassphraseBoundaries.numWords.min), + capitalize: options.capitalize ?? DefaultPassphraseGenerationOptions.capitalize, + number: options.includeNumber ?? DefaultPassphraseGenerationOptions.includeNumber, + separator: options.wordSeparator ?? DefaultPassphraseGenerationOptions.wordSeparator, + }; - // select which word gets the number, if any - let luckyNumber = -1; - if (o.includeNumber) { - luckyNumber = await this.randomizer.uniform(0, o.numWords - 1); - } - - // generate the passphrase - const wordList = new Array(o.numWords); - for (let i = 0; i < o.numWords; i++) { - const word = await this.randomizer.pickWord(EFFLongWordList, { - titleCase: o.capitalize, - number: i === luckyNumber, - }); - - wordList[i] = word; - } - - return wordList.join(o.wordSeparator); + return this.randomizer.randomEffLongWords(request); } } diff --git a/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts b/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts index c1c1355d1ab..536d69c9c15 100644 --- a/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts +++ b/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts @@ -8,8 +8,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { StateProvider } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; -import { Randomizer } from "../abstractions"; import { DefaultPasswordGenerationOptions, DisabledPasswordGeneratorPolicy } from "../data"; +import { PasswordRandomizer } from "../engine"; import { PasswordGeneratorOptionsEvaluator } from "../policies"; import { PasswordGeneratorStrategy } from "./password-generator-strategy"; @@ -66,8 +66,7 @@ describe("Password generation strategy", () => { describe("durableState", () => { it("should use password settings key", () => { const provider = mock(); - const randomizer = mock(); - const strategy = new PasswordGeneratorStrategy(randomizer, provider); + const strategy = new PasswordGeneratorStrategy(null, provider); strategy.durableState(SomeUser); @@ -87,14 +86,390 @@ describe("Password generation strategy", () => { describe("policy", () => { it("should use password generator policy", () => { - const randomizer = mock(); - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(null, null); expect(strategy.policy).toBe(PolicyType.PasswordGenerator); }); }); describe("generate()", () => { - it.todo("should generate a password using the given options"); + const randomizer = mock(); + beforeEach(() => { + randomizer.randomAscii.mockResolvedValue("password"); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it("should map options", async () => { + const strategy = new PasswordGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + length: 20, + ambiguous: true, + uppercase: true, + lowercase: true, + number: true, + special: true, + minUppercase: 1, + minLowercase: 2, + minNumber: 3, + minSpecial: 4, + }); + + expect(result).toEqual("password"); + expect(randomizer.randomAscii).toHaveBeenCalledWith({ + all: 10, + uppercase: 1, + lowercase: 2, + digits: 3, + special: 4, + ambiguous: true, + }); + }); + + it("should disable uppercase", async () => { + const strategy = new PasswordGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + length: 3, + ambiguous: true, + uppercase: false, + lowercase: true, + number: true, + special: true, + minUppercase: 1, + minLowercase: 1, + minNumber: 1, + minSpecial: 1, + }); + + expect(result).toEqual("password"); + expect(randomizer.randomAscii).toHaveBeenCalledWith({ + all: 0, + uppercase: undefined, + lowercase: 1, + digits: 1, + special: 1, + ambiguous: true, + }); + }); + + it("should disable lowercase", async () => { + const strategy = new PasswordGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + length: 3, + ambiguous: true, + uppercase: true, + lowercase: false, + number: true, + special: true, + minUppercase: 1, + minLowercase: 1, + minNumber: 1, + minSpecial: 1, + }); + + expect(result).toEqual("password"); + expect(randomizer.randomAscii).toHaveBeenCalledWith({ + all: 0, + uppercase: 1, + lowercase: undefined, + digits: 1, + special: 1, + ambiguous: true, + }); + }); + + it("should disable digits", async () => { + const strategy = new PasswordGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + length: 3, + ambiguous: true, + uppercase: true, + lowercase: true, + number: false, + special: true, + minUppercase: 1, + minLowercase: 1, + minNumber: 1, + minSpecial: 1, + }); + + expect(result).toEqual("password"); + expect(randomizer.randomAscii).toHaveBeenCalledWith({ + all: 0, + uppercase: 1, + lowercase: 1, + digits: undefined, + special: 1, + ambiguous: true, + }); + }); + + it("should disable special", async () => { + const strategy = new PasswordGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + length: 3, + ambiguous: true, + uppercase: true, + lowercase: true, + number: true, + special: false, + minUppercase: 1, + minLowercase: 1, + minNumber: 1, + minSpecial: 1, + }); + + expect(result).toEqual("password"); + expect(randomizer.randomAscii).toHaveBeenCalledWith({ + all: 0, + uppercase: 1, + lowercase: 1, + digits: 1, + special: undefined, + ambiguous: true, + }); + }); + + it("should override length with minimums", async () => { + const strategy = new PasswordGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + length: 20, + ambiguous: true, + uppercase: true, + lowercase: true, + number: true, + special: true, + minUppercase: 1, + minLowercase: 2, + minNumber: 3, + minSpecial: 4, + }); + + expect(result).toEqual("password"); + expect(randomizer.randomAscii).toHaveBeenCalledWith({ + all: 10, + uppercase: 1, + lowercase: 2, + digits: 3, + special: 4, + ambiguous: true, + }); + }); + + it("should default uppercase", async () => { + const strategy = new PasswordGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + length: 2, + ambiguous: true, + lowercase: true, + number: true, + special: true, + minUppercase: 2, + minLowercase: 0, + minNumber: 0, + minSpecial: 0, + }); + + expect(result).toEqual("password"); + expect(randomizer.randomAscii).toHaveBeenCalledWith({ + all: 0, + uppercase: 2, + lowercase: 0, + digits: 0, + special: 0, + ambiguous: true, + }); + }); + + it("should default lowercase", async () => { + const strategy = new PasswordGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + length: 0, + ambiguous: true, + uppercase: true, + number: true, + special: true, + minUppercase: 0, + minLowercase: 2, + minNumber: 0, + minSpecial: 0, + }); + + expect(result).toEqual("password"); + expect(randomizer.randomAscii).toHaveBeenCalledWith({ + all: 0, + uppercase: 0, + lowercase: 2, + digits: 0, + special: 0, + ambiguous: true, + }); + }); + + it("should default number", async () => { + const strategy = new PasswordGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + length: 0, + ambiguous: true, + uppercase: true, + lowercase: true, + special: true, + minUppercase: 0, + minLowercase: 0, + minNumber: 2, + minSpecial: 0, + }); + + expect(result).toEqual("password"); + expect(randomizer.randomAscii).toHaveBeenCalledWith({ + all: 0, + uppercase: 0, + lowercase: 0, + digits: 2, + special: 0, + ambiguous: true, + }); + }); + + it("should default special", async () => { + const strategy = new PasswordGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + length: 0, + ambiguous: true, + uppercase: true, + lowercase: true, + number: true, + minUppercase: 0, + minLowercase: 0, + minNumber: 0, + minSpecial: 0, + }); + + expect(result).toEqual("password"); + expect(randomizer.randomAscii).toHaveBeenCalledWith({ + all: 0, + uppercase: 0, + lowercase: 0, + digits: 0, + special: undefined, + ambiguous: true, + }); + }); + + it("should default minUppercase", async () => { + const strategy = new PasswordGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + length: 0, + ambiguous: true, + uppercase: true, + lowercase: true, + number: true, + special: true, + minLowercase: 0, + minNumber: 0, + minSpecial: 0, + }); + + expect(result).toEqual("password"); + expect(randomizer.randomAscii).toHaveBeenCalledWith({ + all: 0, + uppercase: 1, + lowercase: 0, + digits: 0, + special: 0, + ambiguous: true, + }); + }); + + it("should default minLowercase", async () => { + const strategy = new PasswordGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + length: 0, + ambiguous: true, + uppercase: true, + lowercase: true, + number: true, + special: true, + minUppercase: 0, + minNumber: 0, + minSpecial: 0, + }); + + expect(result).toEqual("password"); + expect(randomizer.randomAscii).toHaveBeenCalledWith({ + all: 0, + uppercase: 0, + lowercase: 1, + digits: 0, + special: 0, + ambiguous: true, + }); + }); + + it("should default minNumber", async () => { + const strategy = new PasswordGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + length: 0, + ambiguous: true, + uppercase: true, + lowercase: true, + number: true, + special: true, + minUppercase: 0, + minLowercase: 0, + minSpecial: 0, + }); + + expect(result).toEqual("password"); + expect(randomizer.randomAscii).toHaveBeenCalledWith({ + all: 0, + uppercase: 0, + lowercase: 0, + digits: 1, + special: 0, + ambiguous: true, + }); + }); + + it("should default minSpecial", async () => { + const strategy = new PasswordGeneratorStrategy(randomizer, null); + + const result = await strategy.generate({ + length: 0, + ambiguous: true, + uppercase: true, + lowercase: true, + number: true, + special: true, + minUppercase: 0, + minLowercase: 0, + minNumber: 0, + }); + + expect(result).toEqual("password"); + expect(randomizer.randomAscii).toHaveBeenCalledWith({ + all: 0, + uppercase: 0, + lowercase: 0, + digits: 0, + special: 0, + ambiguous: true, + }); + }); }); }); diff --git a/libs/tools/generator/core/src/strategies/password-generator-strategy.ts b/libs/tools/generator/core/src/strategies/password-generator-strategy.ts index d8e59d31057..587c5609e04 100644 --- a/libs/tools/generator/core/src/strategies/password-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/password-generator-strategy.ts @@ -1,11 +1,12 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { StateProvider } from "@bitwarden/common/platform/state"; -import { GeneratorStrategy, Randomizer } from "../abstractions"; +import { GeneratorStrategy } from "../abstractions"; import { Policies, DefaultPasswordGenerationOptions } from "../data"; +import { PasswordRandomizer } from "../engine"; import { mapPolicyToEvaluator } from "../rx"; import { PasswordGenerationOptions, PasswordGeneratorPolicy } from "../types"; -import { clone$PerUserId, sharedStateByUserId } from "../util"; +import { clone$PerUserId, sharedStateByUserId, sum } from "../util"; import { PASSWORD_SETTINGS } from "./storage"; @@ -17,7 +18,7 @@ export class PasswordGeneratorStrategy * @param legacy generates the password */ constructor( - private randomizer: Randomizer, + private randomizer: PasswordRandomizer, private stateProvider: StateProvider, ) {} @@ -31,94 +32,64 @@ export class PasswordGeneratorStrategy // algorithm async generate(options: PasswordGenerationOptions): Promise { - const o = { ...DefaultPasswordGenerationOptions, ...options }; - let positions: string[] = []; - if (o.lowercase && o.minLowercase > 0) { - for (let i = 0; i < o.minLowercase; i++) { - positions.push("l"); - } - } - if (o.uppercase && o.minUppercase > 0) { - for (let i = 0; i < o.minUppercase; i++) { - positions.push("u"); - } - } - if (o.number && o.minNumber > 0) { - for (let i = 0; i < o.minNumber; i++) { - positions.push("n"); - } - } - if (o.special && o.minSpecial > 0) { - for (let i = 0; i < o.minSpecial; i++) { - positions.push("s"); - } - } - while (positions.length < o.length) { - positions.push("a"); + // converts password generation option sets, which are defined by + // an "enabled" and "quantity" parameter, to the password engine's + // parameters, which represent disabled options as `undefined` + // properties. + function process( + // values read from the options + enabled: boolean, + quantity: number, + // value used if an option is missing + defaultEnabled: boolean, + defaultQuantity: number, + ) { + const isEnabled = enabled ?? defaultEnabled; + const actualQuantity = quantity ?? defaultQuantity; + const result = isEnabled ? actualQuantity : undefined; + + return result; } - // shuffle - positions = await this.randomizer.shuffle(positions); + const request = { + uppercase: process( + options.uppercase, + options.minUppercase, + DefaultPasswordGenerationOptions.uppercase, + DefaultPasswordGenerationOptions.minUppercase, + ), + lowercase: process( + options.lowercase, + options.minLowercase, + DefaultPasswordGenerationOptions.lowercase, + DefaultPasswordGenerationOptions.minLowercase, + ), + digits: process( + options.number, + options.minNumber, + DefaultPasswordGenerationOptions.number, + DefaultPasswordGenerationOptions.minNumber, + ), + special: process( + options.special, + options.minSpecial, + DefaultPasswordGenerationOptions.special, + DefaultPasswordGenerationOptions.minSpecial, + ), + ambiguous: options.ambiguous ?? DefaultPasswordGenerationOptions.ambiguous, + all: 0, + }; - // build out the char sets - let allCharSet = ""; + // engine represents character sets as "include only"; you assert how many all + // characters there can be rather than a total length. This conversion has + // the character classes win, so that the result is always consistent with policy + // minimums. + const required = sum(request.uppercase, request.lowercase, request.digits, request.special); + const remaining = (options.length ?? 0) - required; + request.all = Math.max(remaining, 0); - let lowercaseCharSet = "abcdefghijkmnopqrstuvwxyz"; - if (o.ambiguous) { - lowercaseCharSet += "l"; - } - if (o.lowercase) { - allCharSet += lowercaseCharSet; - } + const result = await this.randomizer.randomAscii(request); - let uppercaseCharSet = "ABCDEFGHJKLMNPQRSTUVWXYZ"; - if (o.ambiguous) { - uppercaseCharSet += "IO"; - } - if (o.uppercase) { - allCharSet += uppercaseCharSet; - } - - let numberCharSet = "23456789"; - if (o.ambiguous) { - numberCharSet += "01"; - } - if (o.number) { - allCharSet += numberCharSet; - } - - const specialCharSet = "!@#$%^&*"; - if (o.special) { - allCharSet += specialCharSet; - } - - let password = ""; - for (let i = 0; i < o.length; i++) { - let positionChars: string; - switch (positions[i]) { - case "l": - positionChars = lowercaseCharSet; - break; - case "u": - positionChars = uppercaseCharSet; - break; - case "n": - positionChars = numberCharSet; - break; - case "s": - positionChars = specialCharSet; - break; - case "a": - positionChars = allCharSet; - break; - default: - break; - } - - const randomCharIndex = await this.randomizer.uniform(0, positionChars.length - 1); - password += positionChars.charAt(randomCharIndex); - } - - return password; + return result; } } diff --git a/libs/tools/generator/core/src/strategies/subaddress-generator-strategy.spec.ts b/libs/tools/generator/core/src/strategies/subaddress-generator-strategy.spec.ts index e40832eb725..0be5132c67f 100644 --- a/libs/tools/generator/core/src/strategies/subaddress-generator-strategy.spec.ts +++ b/libs/tools/generator/core/src/strategies/subaddress-generator-strategy.spec.ts @@ -8,8 +8,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { StateProvider } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; -import { Randomizer } from "../abstractions"; import { DefaultSubaddressOptions } from "../data"; +import { EmailCalculator, EmailRandomizer } from "../engine"; import { DefaultPolicyEvaluator } from "../policies"; import { SUBADDRESS_SETTINGS } from "./storage"; @@ -28,7 +28,7 @@ describe("Email subaddress list generation strategy", () => { it.each([[[]], [null], [undefined], [[SomePolicy]], [[SomePolicy, SomePolicy]]])( "should map any input (= %p) to the default policy evaluator", async (policies) => { - const strategy = new SubaddressGeneratorStrategy(null, null); + const strategy = new SubaddressGeneratorStrategy(null, null, null, null); const evaluator$ = of(policies).pipe(strategy.toEvaluator()); const evaluator = await firstValueFrom(evaluator$); @@ -41,8 +41,7 @@ describe("Email subaddress list generation strategy", () => { describe("durableState", () => { it("should use password settings key", () => { const provider = mock(); - const randomizer = mock(); - const strategy = new SubaddressGeneratorStrategy(randomizer, provider); + const strategy = new SubaddressGeneratorStrategy(null, null, provider); strategy.durableState(SomeUser); @@ -52,7 +51,7 @@ describe("Email subaddress list generation strategy", () => { describe("defaults$", () => { it("should return the default subaddress options", async () => { - const strategy = new SubaddressGeneratorStrategy(null, null); + const strategy = new SubaddressGeneratorStrategy(null, null, null); const result = await firstValueFrom(strategy.defaults$(SomeUser)); @@ -62,14 +61,52 @@ describe("Email subaddress list generation strategy", () => { describe("policy", () => { it("should use password generator policy", () => { - const randomizer = mock(); - const strategy = new SubaddressGeneratorStrategy(randomizer, null); + const strategy = new SubaddressGeneratorStrategy(null, null, null); expect(strategy.policy).toBe(PolicyType.PasswordGenerator); }); }); describe("generate()", () => { - it.todo("generate email subaddress tests"); + it("generates a random subaddress by default", async () => { + const randomizer = mock(); + randomizer.randomAsciiSubaddress.mockResolvedValue("subaddress@example.com"); + const strategy = new SubaddressGeneratorStrategy(null, randomizer, null); + + const result = await strategy.generate({ subaddressEmail: "foo@example.com", website: "" }); + + expect(result).toEqual("subaddress@example.com"); + expect(randomizer.randomAsciiSubaddress).toHaveBeenCalledWith("foo@example.com"); + }); + + it("generate random catchall email addresses", async () => { + const randomizer = mock(); + randomizer.randomAsciiSubaddress.mockResolvedValue("subaddress@example.com"); + const strategy = new SubaddressGeneratorStrategy(null, randomizer, null); + + const result = await strategy.generate({ + subaddressType: "random", + subaddressEmail: "foo@example.com", + website: "", + }); + + expect(result).toEqual("subaddress@example.com"); + expect(randomizer.randomAsciiSubaddress).toHaveBeenCalledWith("foo@example.com"); + }); + + it("generate catchall email addresses from website", async () => { + const calculator = mock(); + calculator.appendToSubaddress.mockReturnValue("subaddress@example.com"); + const strategy = new SubaddressGeneratorStrategy(calculator, null, null); + + const result = await strategy.generate({ + subaddressType: "website-name", + subaddressEmail: "foo@example.com", + website: "bar.com", + }); + + expect(result).toEqual("subaddress@example.com"); + expect(calculator.appendToSubaddress).toHaveBeenCalledWith("bar.com", "foo@example.com"); + }); }); }); diff --git a/libs/tools/generator/core/src/strategies/subaddress-generator-strategy.ts b/libs/tools/generator/core/src/strategies/subaddress-generator-strategy.ts index 51d698ea95f..645b15a6501 100644 --- a/libs/tools/generator/core/src/strategies/subaddress-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/subaddress-generator-strategy.ts @@ -1,8 +1,9 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { StateProvider } from "@bitwarden/common/platform/state"; -import { GeneratorStrategy, Randomizer } from "../abstractions"; +import { GeneratorStrategy } from "../abstractions"; import { DefaultSubaddressOptions } from "../data"; +import { EmailCalculator, EmailRandomizer } from "../engine"; import { newDefaultEvaluator } from "../rx"; import { SubaddressGenerationOptions, NoPolicy } from "../types"; import { clone$PerUserId, sharedStateByUserId } from "../util"; @@ -21,7 +22,8 @@ export class SubaddressGeneratorStrategy * @param usernameService generates an email subaddress from an email address */ constructor( - private random: Randomizer, + private emailCalculator: EmailCalculator, + private emailRandomizer: EmailRandomizer, private stateProvider: StateProvider, private defaultOptions: SubaddressGenerationOptions = DefaultSubaddressOptions, ) {} @@ -34,29 +36,14 @@ export class SubaddressGeneratorStrategy // algorithm async generate(options: SubaddressGenerationOptions) { - const o = Object.assign({}, DefaultSubaddressOptions, options); - - const subaddressEmail = o.subaddressEmail; - if (subaddressEmail == null || subaddressEmail.length < 3) { - return o.subaddressEmail; - } - const atIndex = subaddressEmail.indexOf("@"); - if (atIndex < 1 || atIndex >= subaddressEmail.length - 1) { - return subaddressEmail; - } - if (o.subaddressType == null) { - o.subaddressType = "random"; + if (options.subaddressType == null) { + options.subaddressType = "random"; } - const emailBeginning = subaddressEmail.substr(0, atIndex); - const emailEnding = subaddressEmail.substr(atIndex + 1, subaddressEmail.length); - - let subaddressString = ""; - if (o.subaddressType === "random") { - subaddressString = await this.random.chars(8); - } else if (o.subaddressType === "website-name") { - subaddressString = o.website; + if (options.subaddressType === "website-name") { + return this.emailCalculator.appendToSubaddress(options.website, options.subaddressEmail); } - return emailBeginning + "+" + subaddressString + "@" + emailEnding; + + return this.emailRandomizer.randomAsciiSubaddress(options.subaddressEmail); } } diff --git a/libs/tools/generator/core/src/util.spec.ts b/libs/tools/generator/core/src/util.spec.ts new file mode 100644 index 00000000000..32bdc3ad3af --- /dev/null +++ b/libs/tools/generator/core/src/util.spec.ts @@ -0,0 +1,17 @@ +import { sum } from "./util"; + +describe("sum", () => { + it("returns 0 when the list is empty", () => { + expect(sum()).toBe(0); + }); + + it("returns its argument when there's a single number", () => { + expect(sum(1)).toBe(1); + }); + + it("adds its arguments together", () => { + expect(sum(1, 2)).toBe(3); + expect(sum(1, 3)).toBe(4); + expect(sum(1, 2, 3)).toBe(6); + }); +}); diff --git a/libs/tools/generator/core/src/util.ts b/libs/tools/generator/core/src/util.ts index db131d3b482..c62591a2ae8 100644 --- a/libs/tools/generator/core/src/util.ts +++ b/libs/tools/generator/core/src/util.ts @@ -43,3 +43,7 @@ export function sharedByUserId(create: (userId: UserId) => SingleUserStat export function sharedStateByUserId(key: UserKeyDefinition, provider: StateProvider) { return (id: UserId) => provider.getUser(id, key); } + +/** returns the sum of items in the list. */ +export const sum = (...items: number[]) => + (items ?? []).reduce((sum: number, current: number) => sum + (current ?? 0), 0); diff --git a/libs/tools/generator/extensions/legacy/src/create-legacy-password-generation-service.ts b/libs/tools/generator/extensions/legacy/src/create-legacy-password-generation-service.ts index 192b566441a..8ef14a3a9eb 100644 --- a/libs/tools/generator/extensions/legacy/src/create-legacy-password-generation-service.ts +++ b/libs/tools/generator/extensions/legacy/src/create-legacy-password-generation-service.ts @@ -10,9 +10,9 @@ import { DefaultGeneratorNavigationService } from "@bitwarden/generator-navigati import { LegacyPasswordGenerationService } from "./legacy-password-generation.service"; import { PasswordGenerationServiceAbstraction } from "./password-generation.service.abstraction"; -const PassphraseGeneratorStrategy = strategies.PassphraseGeneratorStrategy; -const PasswordGeneratorStrategy = strategies.PasswordGeneratorStrategy; -const CryptoServiceRandomizer = engine.CryptoServiceRandomizer; +const { PassphraseGeneratorStrategy, PasswordGeneratorStrategy } = strategies; +const { CryptoServiceRandomizer, PasswordRandomizer } = engine; + const DefaultGeneratorService = services.DefaultGeneratorService; export function legacyPasswordGenerationServiceFactory( @@ -23,14 +23,15 @@ export function legacyPasswordGenerationServiceFactory( stateProvider: StateProvider, ): PasswordGenerationServiceAbstraction { const randomizer = new CryptoServiceRandomizer(cryptoService); + const passwordRandomizer = new PasswordRandomizer(randomizer); const passwords = new DefaultGeneratorService( - new PasswordGeneratorStrategy(randomizer, stateProvider), + new PasswordGeneratorStrategy(passwordRandomizer, stateProvider), policyService, ); const passphrases = new DefaultGeneratorService( - new PassphraseGeneratorStrategy(randomizer, stateProvider), + new PassphraseGeneratorStrategy(passwordRandomizer, stateProvider), policyService, ); diff --git a/libs/tools/generator/extensions/legacy/src/create-legacy-username-generation-service.ts b/libs/tools/generator/extensions/legacy/src/create-legacy-username-generation-service.ts index 956bc392636..1bcf5403564 100644 --- a/libs/tools/generator/extensions/legacy/src/create-legacy-username-generation-service.ts +++ b/libs/tools/generator/extensions/legacy/src/create-legacy-username-generation-service.ts @@ -11,17 +11,19 @@ import { DefaultGeneratorNavigationService } from "@bitwarden/generator-navigati import { LegacyUsernameGenerationService } from "./legacy-username-generation.service"; import { UsernameGenerationServiceAbstraction } from "./username-generation.service.abstraction"; +const { CryptoServiceRandomizer, UsernameRandomizer, EmailRandomizer, EmailCalculator } = engine; const DefaultGeneratorService = services.DefaultGeneratorService; -const CryptoServiceRandomizer = engine.CryptoServiceRandomizer; -const CatchallGeneratorStrategy = strategies.CatchallGeneratorStrategy; -const SubaddressGeneratorStrategy = strategies.SubaddressGeneratorStrategy; -const EffUsernameGeneratorStrategy = strategies.EffUsernameGeneratorStrategy; -const AddyIoForwarder = strategies.AddyIoForwarder; -const DuckDuckGoForwarder = strategies.DuckDuckGoForwarder; -const FastmailForwarder = strategies.FastmailForwarder; -const FirefoxRelayForwarder = strategies.FirefoxRelayForwarder; -const ForwardEmailForwarder = strategies.ForwardEmailForwarder; -const SimpleLoginForwarder = strategies.SimpleLoginForwarder; +const { + CatchallGeneratorStrategy, + SubaddressGeneratorStrategy, + EffUsernameGeneratorStrategy, + AddyIoForwarder, + DuckDuckGoForwarder, + FastmailForwarder, + FirefoxRelayForwarder, + ForwardEmailForwarder, + SimpleLoginForwarder, +} = strategies; export function legacyUsernameGenerationServiceFactory( apiService: ApiService, @@ -33,19 +35,22 @@ export function legacyUsernameGenerationServiceFactory( stateProvider: StateProvider, ): UsernameGenerationServiceAbstraction { const randomizer = new CryptoServiceRandomizer(cryptoService); + const usernameRandomizer = new UsernameRandomizer(randomizer); + const emailRandomizer = new EmailRandomizer(randomizer); + const emailCalculator = new EmailCalculator(); const effUsername = new DefaultGeneratorService( - new EffUsernameGeneratorStrategy(randomizer, stateProvider), + new EffUsernameGeneratorStrategy(usernameRandomizer, stateProvider), policyService, ); const subaddress = new DefaultGeneratorService( - new SubaddressGeneratorStrategy(randomizer, stateProvider), + new SubaddressGeneratorStrategy(emailCalculator, emailRandomizer, stateProvider), policyService, ); const catchall = new DefaultGeneratorService( - new CatchallGeneratorStrategy(randomizer, stateProvider), + new CatchallGeneratorStrategy(emailCalculator, emailRandomizer, stateProvider), policyService, ); diff --git a/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.spec.ts b/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.spec.ts index 1391381bd06..ebe5f9074c8 100644 --- a/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.spec.ts +++ b/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.spec.ts @@ -638,6 +638,10 @@ describe("LegacyUsernameGenerationService", () => { }); describe("saveOptions", () => { + // this test is awful, but the coupling of the legacy username generator + // would cause the test file's size to bloat to ~2000 loc. Since the legacy + // generators are actively being rewritten, this heinous test seemed the lesser + // of two evils. it("saves option sets to its inner generators", async () => { const account = mockAccountServiceWith(SomeUser); const navigation = createNavigationGenerator({ type: "password" }); @@ -665,7 +669,7 @@ describe("LegacyUsernameGenerationService", () => { simpleLogin, ); - await generator.saveOptions({ + const options: UsernameGeneratorOptions = { type: "catchall", wordCapitalize: true, wordIncludeNumber: false, @@ -685,7 +689,9 @@ describe("LegacyUsernameGenerationService", () => { forwardedSimpleLoginApiKey: "simpleLoginToken", forwardedSimpleLoginBaseUrl: "https://simplelogin.api.example.com", website: null, - }); + }; + + await generator.saveOptions(options); expect(navigation.saveOptions).toHaveBeenCalledWith(SomeUser, { type: "password", @@ -699,18 +705,28 @@ describe("LegacyUsernameGenerationService", () => { website: null, }); + options.type = "word"; + await generator.saveOptions(options); + expect(effUsername.saveOptions).toHaveBeenCalledWith(SomeUser, { wordCapitalize: true, wordIncludeNumber: false, website: null, }); + options.type = "subaddress"; + await generator.saveOptions(options); + expect(subaddress.saveOptions).toHaveBeenCalledWith(SomeUser, { subaddressType: "random", subaddressEmail: "foo@example.com", website: null, }); + options.type = "forwarded"; + options.forwardedService = "anonaddy"; + await generator.saveOptions(options); + expect(addyIo.saveOptions).toHaveBeenCalledWith(SomeUser, { token: "addyIoToken", domain: "addyio.example.com", @@ -718,27 +734,47 @@ describe("LegacyUsernameGenerationService", () => { website: null, }); + options.type = "forwarded"; + options.forwardedService = "duckduckgo"; + await generator.saveOptions(options); + expect(duckDuckGo.saveOptions).toHaveBeenCalledWith(SomeUser, { token: "ddgToken", website: null, }); + options.type = "forwarded"; + options.forwardedService = "fastmail"; + await generator.saveOptions(options); + expect(fastmail.saveOptions).toHaveBeenCalledWith(SomeUser, { token: "fastmailToken", website: null, }); + options.type = "forwarded"; + options.forwardedService = "firefoxrelay"; + await generator.saveOptions(options); + expect(firefoxRelay.saveOptions).toHaveBeenCalledWith(SomeUser, { token: "firefoxToken", website: null, }); + options.type = "forwarded"; + options.forwardedService = "forwardemail"; + await generator.saveOptions(options); + expect(forwardEmail.saveOptions).toHaveBeenCalledWith(SomeUser, { token: "forwardEmailToken", domain: "example.com", website: null, }); + options.type = "forwarded"; + options.forwardedService = "simplelogin"; + await generator.saveOptions(options); + expect(simpleLogin.saveOptions).toHaveBeenCalledWith(SomeUser, { token: "simpleLoginToken", baseUrl: "https://simplelogin.api.example.com", diff --git a/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.ts b/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.ts index fa1dbf94956..ee9e5bce311 100644 --- a/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.ts +++ b/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.ts @@ -1,6 +1,7 @@ import { zip, firstValueFrom, map, concatMap, combineLatest } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { ApiOptions, EmailDomainOptions, @@ -13,6 +14,8 @@ import { EffUsernameGenerationOptions, Forwarders, SubaddressGenerationOptions, + UsernameGeneratorType, + ForwarderId, } from "@bitwarden/generator-core"; import { GeneratorNavigationService, GeneratorNavigation } from "@bitwarden/generator-navigation"; @@ -178,28 +181,76 @@ export class LegacyUsernameGenerationService implements UsernameGenerationServic const stored = this.toStoredOptions(options); const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + const saved = await this.saveGeneratorOptions(activeAccount.id, options.type, stored); + if (!saved) { + await this.saveForwarderOptions(activeAccount.id, options.forwardedService, stored); + } + + // run navigation options 2nd so that navigation options update doesn't race the `saved options` + // update in Firefox. + await this.saveNavigationOptions(activeAccount.id, stored); + } + + private async saveNavigationOptions(account: UserId, options: MappedOptions) { // generator settings needs to preserve whether password or passphrase is selected, // so `navigationOptions` is mutated. const navigationOptions$ = zip( - this.navigation.options$(activeAccount.id), - this.navigation.defaults$(activeAccount.id), + this.navigation.options$(account), + this.navigation.defaults$(account), ).pipe(map(([options, defaults]) => options ?? defaults)); - let navigationOptions = await firstValueFrom(navigationOptions$); - navigationOptions = Object.assign(navigationOptions, stored.generator); - await this.navigation.saveOptions(activeAccount.id, navigationOptions); - // overwrite all other settings with latest values - await Promise.all([ - this.catchall.saveOptions(activeAccount.id, stored.algorithms.catchall), - this.effUsername.saveOptions(activeAccount.id, stored.algorithms.effUsername), - this.subaddress.saveOptions(activeAccount.id, stored.algorithms.subaddress), - this.addyIo.saveOptions(activeAccount.id, stored.forwarders.addyIo), - this.duckDuckGo.saveOptions(activeAccount.id, stored.forwarders.duckDuckGo), - this.fastmail.saveOptions(activeAccount.id, stored.forwarders.fastmail), - this.firefoxRelay.saveOptions(activeAccount.id, stored.forwarders.firefoxRelay), - this.forwardEmail.saveOptions(activeAccount.id, stored.forwarders.forwardEmail), - this.simpleLogin.saveOptions(activeAccount.id, stored.forwarders.simpleLogin), - ]); + let navigationOptions = await firstValueFrom(navigationOptions$); + navigationOptions = Object.assign(navigationOptions, options.generator); + await this.navigation.saveOptions(account, navigationOptions); + } + + private async saveGeneratorOptions( + account: UserId, + type: UsernameGeneratorType, + options: MappedOptions, + ) { + switch (type) { + case "word": + await this.effUsername.saveOptions(account, options.algorithms.effUsername); + return true; + case "subaddress": + await this.subaddress.saveOptions(account, options.algorithms.subaddress); + return true; + case "catchall": + await this.catchall.saveOptions(account, options.algorithms.catchall); + return true; + default: + return false; + } + } + + private async saveForwarderOptions( + account: UserId, + forwarder: ForwarderId | "", + options: MappedOptions, + ) { + switch (forwarder) { + case "anonaddy": + await this.addyIo.saveOptions(account, options.forwarders.addyIo); + return true; + case "duckduckgo": + await this.duckDuckGo.saveOptions(account, options.forwarders.duckDuckGo); + return true; + case "fastmail": + await this.fastmail.saveOptions(account, options.forwarders.fastmail); + return true; + case "firefoxrelay": + await this.firefoxRelay.saveOptions(account, options.forwarders.firefoxRelay); + return true; + case "forwardemail": + await this.forwardEmail.saveOptions(account, options.forwarders.forwardEmail); + return true; + case "simplelogin": + await this.simpleLogin.saveOptions(account, options.forwarders.simpleLogin); + return true; + default: + return false; + } } private toStoredOptions(options: UsernameGeneratorOptions) { diff --git a/libs/tools/send/send-ui/jest.config.js b/libs/tools/send/send-ui/jest.config.js index 100075fc7a7..b68bda8d5ca 100644 --- a/libs/tools/send/send-ui/jest.config.js +++ b/libs/tools/send/send-ui/jest.config.js @@ -5,9 +5,9 @@ const { compilerOptions } = require("../../../shared/tsconfig.libs"); /** @type {import('jest').Config} */ module.exports = { testMatch: ["**/+(*.)+(spec).+(ts)"], - preset: "ts-jest", - testEnvironment: "jsdom", + preset: "jest-preset-angular", + setupFilesAfterEnv: ["/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "/../../../", + prefix: "/../../", }), }; diff --git a/libs/tools/send/send-ui/src/index.ts b/libs/tools/send/send-ui/src/index.ts index fc7c87449dd..c85d646bbd1 100644 --- a/libs/tools/send/send-ui/src/index.ts +++ b/libs/tools/send/send-ui/src/index.ts @@ -1,2 +1,4 @@ export * from "./icons"; +export * from "./send-form"; export { NewSendDropdownComponent } from "./new-send-dropdown/new-send-dropdown.component"; +export { SendListFiltersComponent } from "./send-list-filters/send-list-filters.component"; diff --git a/libs/tools/send/send-ui/src/send-form/abstractions/send-form-config.service.ts b/libs/tools/send/send-ui/src/send-form/abstractions/send-form-config.service.ts new file mode 100644 index 00000000000..0859986664a --- /dev/null +++ b/libs/tools/send/send-ui/src/send-form/abstractions/send-form-config.service.ts @@ -0,0 +1,93 @@ +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { Send } from "@bitwarden/common/tools/send/models/domain/send"; +import { SendId } from "@bitwarden/common/types/guid"; + +/** + * The mode of the add/edit form. + * - `add` - The form is creating a new send. + * - `edit` - The form is editing an existing send. + * - `partial-edit` - The form is editing an existing send, but only the favorite/folder fields + */ +export type SendFormMode = "add" | "edit" | "partial-edit"; + +/** + * Base configuration object for the send form. Includes all common fields. + */ +type BaseSendFormConfig = { + /** + * The mode of the form. + */ + mode: SendFormMode; + + /** + * The type of send to create/edit. + */ + sendType: SendType; + + /** + * Flag to indicate if the user is allowed to create sends. If false, configuration must + * supply a list of organizations that the user can create sends in. + */ + areSendsAllowed: boolean; + + /** + * The original send that is being edited or cloned. This can be undefined when creating a new send. + */ + originalSend?: Send; +}; + +/** + * Configuration object for the send form when editing an existing send. + */ +type ExistingSendConfig = BaseSendFormConfig & { + mode: "edit" | "partial-edit"; + originalSend: Send; +}; + +/** + * Configuration object for the send form when creating a completely new send. + */ +type CreateNewSendConfig = BaseSendFormConfig & { + mode: "add"; +}; + +type CombinedAddEditConfig = ExistingSendConfig | CreateNewSendConfig; + +/** + * Configuration object for the send form when personal ownership is allowed. + */ +type SendsAllowed = CombinedAddEditConfig & { + areSendsAllowed: true; +}; + +/** + * Configuration object for the send form when Sends are not allowed by an organization. + * Organizations must be provided. + */ +type SendsNotAllowed = CombinedAddEditConfig & { + areSendsAllowed: false; +}; + +/** + * Configuration object for the send form. + * Determines the behavior of the form and the controls that are displayed/enabled. + */ +export type SendFormConfig = SendsAllowed | SendsNotAllowed; + +/** + * Service responsible for building the configuration object for the send form. + */ +export abstract class SendFormConfigService { + /** + * Builds the configuration for the send form using the specified mode, sendId, and sendType. + * The other configuration fields will be fetched from their respective services. + * @param mode + * @param sendId + * @param sendType + */ + abstract buildConfig( + mode: SendFormMode, + sendId?: SendId, + sendType?: SendType, + ): Promise; +} diff --git a/libs/tools/send/send-ui/src/send-form/abstractions/send-form.service.ts b/libs/tools/send/send-ui/src/send-form/abstractions/send-form.service.ts new file mode 100644 index 00000000000..b851db927b4 --- /dev/null +++ b/libs/tools/send/send-ui/src/send-form/abstractions/send-form.service.ts @@ -0,0 +1,26 @@ +import { Send } from "@bitwarden/common/tools/send/models/domain/send"; +import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; + +import { SendFormConfig } from "./send-form-config.service"; + +/** + * Service to save the send using the correct endpoint(s) and encapsulating the logic for decrypting the send. + * + * This service should only be used internally by the SendFormComponent. + */ +export abstract class SendFormService { + /** + * Helper to decrypt a send and avoid the need to call the send service directly. + * (useful for mocking tests/storybook). + */ + abstract decryptSend(send: Send): Promise; + + /** + * Saves the new or modified send with the server. + */ + abstract saveSend( + send: SendView, + file: File | ArrayBuffer, + config: SendFormConfig, + ): Promise; +} diff --git a/libs/tools/send/send-ui/src/send-form/components/send-form.component.html b/libs/tools/send/send-ui/src/send-form/components/send-form.component.html new file mode 100644 index 00000000000..2ed7ef4d4f1 --- /dev/null +++ b/libs/tools/send/send-ui/src/send-form/components/send-form.component.html @@ -0,0 +1,4 @@ +
+ + +
diff --git a/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts new file mode 100644 index 00000000000..270d610c8a1 --- /dev/null +++ b/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts @@ -0,0 +1,210 @@ +import { NgIf } from "@angular/common"; +import { + AfterViewInit, + Component, + DestroyRef, + EventEmitter, + forwardRef, + inject, + Input, + OnChanges, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; +import { + AsyncActionsModule, + BitSubmitDirective, + ButtonComponent, + CardComponent, + FormFieldModule, + ItemModule, + SectionComponent, + SelectModule, + ToastService, + TypographyModule, +} from "@bitwarden/components"; + +import { SendFormConfig } from "../abstractions/send-form-config.service"; +import { SendFormService } from "../abstractions/send-form.service"; +import { SendForm, SendFormContainer } from "../send-form-container"; + +@Component({ + selector: "tools-send-form", + templateUrl: "./send-form.component.html", + standalone: true, + providers: [ + { + provide: SendFormContainer, + useExisting: forwardRef(() => SendFormComponent), + }, + ], + imports: [ + AsyncActionsModule, + CardComponent, + SectionComponent, + TypographyModule, + ItemModule, + FormFieldModule, + ReactiveFormsModule, + SelectModule, + NgIf, + ], +}) +export class SendFormComponent implements AfterViewInit, OnInit, OnChanges, SendFormContainer { + @ViewChild(BitSubmitDirective) + private bitSubmit: BitSubmitDirective; + private destroyRef = inject(DestroyRef); + private _firstInitialized = false; + + /** + * The form ID to use for the form. Used to connect it to a submit button. + */ + @Input({ required: true }) formId: string; + + /** + * The configuration for the add/edit form. Used to determine which controls are shown and what values are available. + */ + @Input({ required: true }) config: SendFormConfig; + + /** + * Optional submit button that will be disabled or marked as loading when the form is submitting. + */ + @Input() + submitBtn?: ButtonComponent; + + /** + * Event emitted when the send is saved successfully. + */ + @Output() sendSaved = new EventEmitter(); + + /** + * The original send being edited or cloned. Null for add mode. + */ + originalSendView: SendView | null; + + /** + * The form group for the send. Starts empty and is populated by child components via the `registerChildForm` method. + * @protected + */ + protected sendForm = this.formBuilder.group({}); + + /** + * The value of the updated send. Starts as a new send and is updated + * by child components via the `patchSend` method. + * @protected + */ + protected updatedSendView: SendView | null; + protected loading: boolean = true; + + SendType = SendType; + + ngAfterViewInit(): void { + if (this.submitBtn) { + this.bitSubmit.loading$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((loading) => { + this.submitBtn.loading = loading; + }); + + this.bitSubmit.disabled$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((disabled) => { + this.submitBtn.disabled = disabled; + }); + } + } + + /** + * Registers a child form group with the parent form group. Used by child components to add their form groups to + * the parent form for validation. + * @param name - The name of the form group. + * @param group - The form group to add. + */ + registerChildForm( + name: K, + group: Exclude, + ): void { + this.sendForm.setControl(name, group); + } + + /** + * Patches the updated send with the provided partial senbd. Used by child components to update the send + * as their form values change. + * @param send + */ + patchSend(send: Partial): void { + this.updatedSendView = Object.assign(this.updatedSendView, send); + } + + /** + * We need to re-initialize the form when the config is updated. + */ + async ngOnChanges() { + // Avoid re-initializing the form on the first change detection cycle. + if (this._firstInitialized) { + await this.init(); + } + } + + async ngOnInit() { + await this.init(); + this._firstInitialized = true; + } + + async init() { + this.loading = true; + this.updatedSendView = new SendView(); + this.originalSendView = null; + this.sendForm.reset(); + + if (this.config == null) { + return; + } + + if (this.config.mode !== "add") { + if (this.config.originalSend == null) { + throw new Error("Original send is required for edit or clone mode"); + } + + this.originalSendView = await this.addEditFormService.decryptSend(this.config.originalSend); + + this.updatedSendView = Object.assign(this.updatedSendView, this.originalSendView); + } else { + this.updatedSendView.type = this.config.sendType; + } + + this.loading = false; + } + + constructor( + private formBuilder: FormBuilder, + private addEditFormService: SendFormService, + private toastService: ToastService, + private i18nService: I18nService, + ) {} + + submit = async () => { + if (this.sendForm.invalid) { + this.sendForm.markAllAsTouched(); + return; + } + + // TODO: Add file handling + await this.addEditFormService.saveSend(this.updatedSendView, null, this.config); + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t( + this.config.mode === "edit" || this.config.mode === "partial-edit" + ? "editedItem" + : "addedItem", + ), + }); + + this.sendSaved.emit(this.updatedSendView); + }; +} diff --git a/libs/tools/send/send-ui/src/send-form/index.ts b/libs/tools/send/send-ui/src/send-form/index.ts new file mode 100644 index 00000000000..06f163dec40 --- /dev/null +++ b/libs/tools/send/send-ui/src/send-form/index.ts @@ -0,0 +1,7 @@ +export { SendFormModule } from "./send-form.module"; +export { + SendFormConfigService, + SendFormConfig, + SendFormMode, +} from "./abstractions/send-form-config.service"; +export { DefaultSendFormConfigService } from "./services/default-send-form-config.service"; diff --git a/libs/tools/send/send-ui/src/send-form/send-form-container.ts b/libs/tools/send/send-ui/src/send-form/send-form-container.ts new file mode 100644 index 00000000000..01983360e3e --- /dev/null +++ b/libs/tools/send/send-ui/src/send-form/send-form-container.ts @@ -0,0 +1,36 @@ +import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; + +import { SendFormConfig } from "./abstractions/send-form-config.service"; +/** + * The complete form for a send. Includes all the sub-forms from their respective section components. + * TODO: Add additional form sections as they are implemented. + */ +export type SendForm = object; + +/** + * A container for the {@link SendForm} that allows for registration of child form groups and patching of the send + * to be updated/created. Child form components inject this container in order to register themselves with the parent form + * and access configuration options. + * + * This is an alternative to passing the form groups down through the component tree via @Inputs() and form updates via + * @Outputs(). It allows child forms to define their own structure and validation rules, while still being able to + * update the parent send. + */ +export abstract class SendFormContainer { + /** + * The configuration for the send form. + */ + readonly config: SendFormConfig; + + /** + * The original send that is being edited/cloned. Used to pre-populate the form and compare changes. + */ + readonly originalSendView: SendView | null; + + abstract registerChildForm( + name: K, + group: Exclude, + ): void; + + abstract patchSend(send: Partial): void; +} diff --git a/libs/tools/send/send-ui/src/send-form/send-form.mdx b/libs/tools/send/send-ui/src/send-form/send-form.mdx new file mode 100644 index 00000000000..d1297ee90ca --- /dev/null +++ b/libs/tools/send/send-ui/src/send-form/send-form.mdx @@ -0,0 +1,17 @@ +import { Controls, Meta, Primary } from "@storybook/addon-docs"; + +import * as stories from "./send-form.stories"; + + + +# Send Form + +The send form is a re-usable form component that can be used to create, update, and clone sends. It +is configured via a `SendFormConfig` object that is passed to the component as a prop. The +`SendFormConfig` object can be created manually, or a `SendFormConfigService` can be used to create +it. A default implementation of the `SendFormConfigService` exists in the `@bitwarden/send-ui` +library. + + + + diff --git a/libs/tools/send/send-ui/src/send-form/send-form.module.ts b/libs/tools/send/send-ui/src/send-form/send-form.module.ts new file mode 100644 index 00000000000..8b004207c1b --- /dev/null +++ b/libs/tools/send/send-ui/src/send-form/send-form.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from "@angular/core"; + +import { SendFormService } from "./abstractions/send-form.service"; +import { SendFormComponent } from "./components/send-form.component"; +import { DefaultSendFormService } from "./services/default-send-form.service"; + +@NgModule({ + imports: [SendFormComponent], + providers: [ + { + provide: SendFormService, + useClass: DefaultSendFormService, + }, + ], + exports: [SendFormComponent], +}) +export class SendFormModule {} diff --git a/libs/tools/send/send-ui/src/send-form/send-form.stories.ts b/libs/tools/send/send-ui/src/send-form/send-form.stories.ts new file mode 100644 index 00000000000..044b26577ee --- /dev/null +++ b/libs/tools/send/send-ui/src/send-form/send-form.stories.ts @@ -0,0 +1,132 @@ +import { importProvidersFrom } from "@angular/core"; +import { action } from "@storybook/addon-actions"; +import { + applicationConfig, + componentWrapperDecorator, + Meta, + moduleMetadata, + StoryObj, +} from "@storybook/angular"; + +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { Send } from "@bitwarden/common/tools/send/models/domain/send"; +import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; +import { AsyncActionsModule, ButtonModule, ToastService } from "@bitwarden/components"; +import { SendFormConfig } from "@bitwarden/send-ui"; +import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/src/app/core/tests"; + +import { SendFormService } from "./abstractions/send-form.service"; +import { SendFormComponent } from "./components/send-form.component"; +import { SendFormModule } from "./send-form.module"; + +const defaultConfig: SendFormConfig = { + mode: "add", + sendType: SendType.Text, + areSendsAllowed: true, + originalSend: { + id: "123", + name: "Test Send", + notes: "Example notes", + } as unknown as Send, +}; + +class TestAddEditFormService implements SendFormService { + decryptSend(): Promise { + return Promise.resolve(defaultConfig.originalSend as any); + } + async saveSend(send: SendView, file: File | ArrayBuffer): Promise { + await new Promise((resolve) => setTimeout(resolve, 1000)); + return send; + } +} + +const actionsData = { + onSave: action("onSave"), +}; + +export default { + title: "Tools/Send Form", + component: SendFormComponent, + decorators: [ + moduleMetadata({ + imports: [SendFormModule, AsyncActionsModule, ButtonModule], + providers: [ + { + provide: SendFormService, + useClass: TestAddEditFormService, + }, + { + provide: ToastService, + useValue: { + showToast: action("showToast"), + }, + }, + ], + }), + componentWrapperDecorator( + (story) => `
${story}
`, + ), + applicationConfig({ + providers: [importProvidersFrom(PreloadedEnglishI18nModule)], + }), + ], + args: { + config: defaultConfig, + }, + argTypes: { + config: { + description: "The configuration object for the form.", + }, + }, +} as Meta; + +type Story = StoryObj; + +export const Default: Story = { + render: (args) => { + return { + props: { + onSave: actionsData.onSave, + ...args, + }, + template: /*html*/ ` + + + `, + }; + }, +}; + +export const Edit: Story = { + ...Default, + args: { + config: { + ...defaultConfig, + mode: "edit", + originalSend: defaultConfig.originalSend, + }, + }, +}; + +export const PartialEdit: Story = { + ...Default, + args: { + config: { + ...defaultConfig, + mode: "partial-edit", + originalSend: defaultConfig.originalSend, + }, + }, +}; + +export const SendsHaveBeenDisabledByPolicy: Story = { + ...Default, + args: { + config: { + ...defaultConfig, + mode: "add", + areSendsAllowed: false, + originalSend: defaultConfig.originalSend, + }, + }, +}; diff --git a/libs/tools/send/send-ui/src/send-form/services/default-send-form-config.service.ts b/libs/tools/send/send-ui/src/send-form/services/default-send-form-config.service.ts new file mode 100644 index 00000000000..5470fe74ced --- /dev/null +++ b/libs/tools/send/send-ui/src/send-form/services/default-send-form-config.service.ts @@ -0,0 +1,51 @@ +import { inject, Injectable } from "@angular/core"; +import { combineLatest, firstValueFrom, map } from "rxjs"; + +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; +import { SendId } from "@bitwarden/common/types/guid"; + +import { + SendFormConfig, + SendFormConfigService, + SendFormMode, +} from "../abstractions/send-form-config.service"; + +/** + * Default implementation of the `SendFormConfigService`. + */ +@Injectable() +export class DefaultSendFormConfigService implements SendFormConfigService { + private policyService: PolicyService = inject(PolicyService); + private sendService: SendService = inject(SendService); + + async buildConfig( + mode: SendFormMode, + sendId?: SendId, + sendType?: SendType, + ): Promise { + const [areSendsAllowed, send] = await firstValueFrom( + combineLatest([this.areSendsEnabled$, this.getSend(sendId)]), + ); + + return { + mode, + sendType: sendType, + areSendsAllowed, + originalSend: send, + }; + } + + private areSendsEnabled$ = this.policyService + .policyAppliesToActiveUser$(PolicyType.DisableSend) + .pipe(map((p) => !p)); + + private getSend(id?: SendId) { + if (id == null) { + return Promise.resolve(null); + } + return this.sendService.get$(id); + } +} diff --git a/libs/tools/send/send-ui/src/send-form/services/default-send-form.service.ts b/libs/tools/send/send-ui/src/send-form/services/default-send-form.service.ts new file mode 100644 index 00000000000..a94fadc708f --- /dev/null +++ b/libs/tools/send/send-ui/src/send-form/services/default-send-form.service.ts @@ -0,0 +1,29 @@ +import { inject, Injectable } from "@angular/core"; + +import { Send } from "@bitwarden/common/tools/send/models/domain/send"; +import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; +import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; + +import { SendFormConfig } from "../abstractions/send-form-config.service"; +import { SendFormService } from "../abstractions/send-form.service"; + +@Injectable() +export class DefaultSendFormService implements SendFormService { + private sendApiService: SendApiService = inject(SendApiService); + private sendService = inject(SendService); + + async decryptSend(send: Send): Promise { + return await send.decrypt(); + } + + async saveSend( + send: SendView, + file: File | ArrayBuffer, + config: SendFormConfig, + ): Promise { + const sendData = await this.sendService.encrypt(send, file, send.password, null); + const savedSend = await this.sendApiService.save(sendData); + return await savedSend.decrypt(); + } +} diff --git a/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.html b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.html new file mode 100644 index 00000000000..e74e2f05627 --- /dev/null +++ b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.html @@ -0,0 +1,11 @@ +
+
+ + +
+
diff --git a/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.ts b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.ts new file mode 100644 index 00000000000..ccdaa293241 --- /dev/null +++ b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.ts @@ -0,0 +1,25 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnDestroy } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ChipSelectComponent } from "@bitwarden/components"; + +import { SendListFiltersService } from "../services/send-list-filters.service"; + +@Component({ + standalone: true, + selector: "app-send-list-filters", + templateUrl: "./send-list-filters.component.html", + imports: [CommonModule, JslibModule, ChipSelectComponent, ReactiveFormsModule], +}) +export class SendListFiltersComponent implements OnDestroy { + protected filterForm = this.sendListFiltersService.filterForm; + protected sendTypes = this.sendListFiltersService.sendTypes; + + constructor(private sendListFiltersService: SendListFiltersService) {} + + ngOnDestroy(): void { + this.sendListFiltersService.resetFilterForm(); + } +} diff --git a/libs/tools/send/send-ui/src/services/send-list-filters.service.spec.ts b/libs/tools/send/send-ui/src/services/send-list-filters.service.spec.ts new file mode 100644 index 00000000000..023d0e32145 --- /dev/null +++ b/libs/tools/send/send-ui/src/services/send-list-filters.service.spec.ts @@ -0,0 +1,78 @@ +import { TestBed } from "@angular/core/testing"; +import { FormBuilder } from "@angular/forms"; +import { BehaviorSubject, first } from "rxjs"; + +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { Send } from "@bitwarden/common/tools/send/models/domain/send"; + +import { SendListFiltersService } from "./send-list-filters.service"; + +describe("SendListFiltersService", () => { + let service: SendListFiltersService; + const policyAppliesToActiveUser$ = new BehaviorSubject(false); + + const i18nService = { + t: (key: string) => key, + } as I18nService; + + const policyService = { + policyAppliesToActiveUser$: jest.fn(() => policyAppliesToActiveUser$), + }; + + beforeEach(() => { + policyAppliesToActiveUser$.next(false); + policyService.policyAppliesToActiveUser$.mockClear(); + + TestBed.configureTestingModule({ + providers: [ + { + provide: I18nService, + useValue: i18nService, + }, + { + provide: PolicyService, + useValue: policyService, + }, + { provide: FormBuilder, useClass: FormBuilder }, + ], + }); + + service = TestBed.inject(SendListFiltersService); + }); + + it("returns all send types", () => { + expect(service.sendTypes.map((c) => c.value)).toEqual([SendType.File, SendType.Text]); + }); + + it("filters disabled sends", (done) => { + const sends = [{ disabled: true }, { disabled: false }, { disabled: true }] as Send[]; + service.filterFunction$.pipe(first()).subscribe((filterFunction) => { + expect(filterFunction(sends)).toEqual([sends[1]]); + done(); + }); + + service.filterForm.patchValue({}); + }); + + it("resets the filter form", () => { + service.filterForm.patchValue({ sendType: SendType.Text }); + service.resetFilterForm(); + expect(service.filterForm.value).toEqual({ sendType: null }); + }); + + it("filters by sendType", (done) => { + const sends = [ + { type: SendType.File }, + { type: SendType.Text }, + { type: SendType.File }, + ] as Send[]; + service.filterFunction$.subscribe((filterFunction) => { + expect(filterFunction(sends)).toEqual([sends[1]]); + done(); + }); + + service.filterForm.patchValue({ sendType: SendType.Text }); + }); +}); diff --git a/libs/tools/send/send-ui/src/services/send-list-filters.service.ts b/libs/tools/send/send-ui/src/services/send-list-filters.service.ts new file mode 100644 index 00000000000..0d2763b880d --- /dev/null +++ b/libs/tools/send/send-ui/src/services/send-list-filters.service.ts @@ -0,0 +1,98 @@ +import { Injectable } from "@angular/core"; +import { FormBuilder } from "@angular/forms"; +import { map, Observable, startWith } from "rxjs"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { Send } from "@bitwarden/common/tools/send/models/domain/send"; +import { ITreeNodeObject, TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; +import { ChipSelectOption } from "@bitwarden/components"; + +export type SendListFilter = { + sendType: SendType | null; +}; + +const INITIAL_FILTERS: SendListFilter = { + sendType: null, +}; + +@Injectable({ + providedIn: "root", +}) +export class SendListFiltersService { + /** + * UI form for all filters + */ + filterForm = this.formBuilder.group(INITIAL_FILTERS); + + /** + * Observable for `filterForm` value + */ + filters$ = this.filterForm.valueChanges.pipe( + startWith(INITIAL_FILTERS), + ) as Observable; + + constructor( + private i18nService: I18nService, + private formBuilder: FormBuilder, + ) {} + + /** + * Observable whose value is a function that filters an array of `Send` objects based on the current filters + */ + filterFunction$: Observable<(send: Send[]) => Send[]> = this.filters$.pipe( + map( + (filters) => (sends: Send[]) => + sends.filter((send) => { + // do not show disabled sends + if (send.disabled) { + return false; + } + + if (filters.sendType !== null && send.type !== filters.sendType) { + return false; + } + + return true; + }), + ), + ); + + /** + * All available send types + */ + readonly sendTypes: ChipSelectOption[] = [ + { + value: SendType.File, + label: this.i18nService.t("file"), + icon: "bwi-file", + }, + { + value: SendType.Text, + label: this.i18nService.t("text"), + icon: "bwi-file-text", + }, + ]; + + /** Resets `filterForm` to the original state */ + resetFilterForm(): void { + this.filterForm.reset(INITIAL_FILTERS); + } + + /** + * Converts the given item into the `ChipSelectOption` structure + */ + private convertToChipSelectOption( + item: TreeNode, + icon: string, + ): ChipSelectOption { + return { + value: item.node, + label: item.node.name, + icon, + children: item.children + ? item.children.map((i) => this.convertToChipSelectOption(i, icon)) + : undefined, + }; + } +} diff --git a/libs/tools/send/send-ui/test.setup.ts b/libs/tools/send/send-ui/test.setup.ts new file mode 100644 index 00000000000..a702c633967 --- /dev/null +++ b/libs/tools/send/send-ui/test.setup.ts @@ -0,0 +1 @@ +import "jest-preset-angular/setup-jest"; diff --git a/libs/tools/send/send-ui/tsconfig.spec.json b/libs/tools/send/send-ui/tsconfig.spec.json index fc8520e7376..919530506de 100644 --- a/libs/tools/send/send-ui/tsconfig.spec.json +++ b/libs/tools/send/send-ui/tsconfig.spec.json @@ -1,3 +1,6 @@ { - "extends": "./tsconfig.json" + "extends": "./tsconfig.json", + "include": ["src"], + "files": ["./test.setup.ts"], + "exclude": ["node_modules", "dist"] } diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts index e90c46fa0ad..40b1cdf6109 100644 --- a/libs/vault/src/cipher-form/cipher-form.stories.ts +++ b/libs/vault/src/cipher-form/cipher-form.stories.ts @@ -86,7 +86,7 @@ const defaultConfig: CipherFormConfig = { password: "testpassword", fido2Credentials: [ { - creationDate: new Date(), + creationDate: new Date(2024, 6, 18), }, ], totp: "123456", diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.html b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.html similarity index 100% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.html rename to libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.html diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.spec.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts similarity index 98% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.spec.ts rename to libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts index 42c6c530eec..5a2d571c802 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.spec.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts @@ -13,10 +13,10 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ButtonComponent, ToastService } from "@bitwarden/components"; +import { DownloadAttachmentComponent } from "@bitwarden/vault"; import { CipherAttachmentsComponent } from "./cipher-attachments.component"; import { DeleteAttachmentComponent } from "./delete-attachment/delete-attachment.component"; -import { DownloadAttachmentComponent } from "./download-attachment/download-attachment.component"; @Component({ standalone: true, diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts similarity index 98% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.ts rename to libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts index 21115955653..000963d1849 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts @@ -39,8 +39,9 @@ import { TypographyModule, } from "@bitwarden/components"; +import { DownloadAttachmentComponent } from "../../../components/download-attachment/download-attachment.component"; + import { DeleteAttachmentComponent } from "./delete-attachment/delete-attachment.component"; -import { DownloadAttachmentComponent } from "./download-attachment/download-attachment.component"; type CipherAttachmentForm = FormGroup<{ file: FormControl; diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/delete-attachment/delete-attachment.component.html b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.html similarity index 100% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/delete-attachment/delete-attachment.component.html rename to libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.html diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/delete-attachment/delete-attachment.component.spec.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts similarity index 100% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/delete-attachment/delete-attachment.component.spec.ts rename to libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/delete-attachment/delete-attachment.component.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts similarity index 100% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/delete-attachment/delete-attachment.component.ts rename to libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts diff --git a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html index 5f33d10b7d5..ff94548b342 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html +++ b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html @@ -28,7 +28,7 @@
-
+ diff --git a/libs/vault/src/cipher-view/additional-information/additional-information.component.ts b/libs/vault/src/cipher-view/additional-information/additional-information.component.ts index 9e1376a8066..ce196b3f572 100644 --- a/libs/vault/src/cipher-view/additional-information/additional-information.component.ts +++ b/libs/vault/src/cipher-view/additional-information/additional-information.component.ts @@ -9,6 +9,7 @@ import { SectionComponent, SectionHeaderComponent, TypographyModule, + FormFieldModule, } from "@bitwarden/components"; @Component({ @@ -24,6 +25,7 @@ import { SectionComponent, SectionHeaderComponent, TypographyModule, + FormFieldModule, ], }) export class AdditionalInformationComponent { diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html new file mode 100644 index 00000000000..467dd690c64 --- /dev/null +++ b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html @@ -0,0 +1,22 @@ + + +

{{ "attachments" | i18n }}

+
+ + + + {{ attachment.fileName }} + {{ attachment.sizeName }} + + + + + + + + +
diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts new file mode 100644 index 00000000000..d7af28cb1ef --- /dev/null +++ b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts @@ -0,0 +1,73 @@ +import { CommonModule } from "@angular/common"; +import { Component, Input } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { NEVER, switchMap } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { StateProvider } from "@bitwarden/common/platform/state"; +import { OrganizationId } from "@bitwarden/common/types/guid"; +import { OrgKey } from "@bitwarden/common/types/key"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + ItemModule, + IconButtonModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, +} from "@bitwarden/components"; + +import { DownloadAttachmentComponent } from "../../components/download-attachment/download-attachment.component"; + +@Component({ + selector: "app-attachments-v2-view", + templateUrl: "attachments-v2-view.component.html", + standalone: true, + imports: [ + CommonModule, + JslibModule, + ItemModule, + IconButtonModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, + DownloadAttachmentComponent, + ], +}) +export class AttachmentsV2ViewComponent { + @Input() cipher: CipherView; + + canAccessPremium: boolean; + orgKey: OrgKey; + + constructor( + private cryptoService: CryptoService, + private billingAccountProfileStateService: BillingAccountProfileStateService, + private stateProvider: StateProvider, + ) { + this.subscribeToHasPremiumCheck(); + this.subscribeToOrgKey(); + } + + subscribeToHasPremiumCheck() { + this.billingAccountProfileStateService.hasPremiumFromAnySource$ + .pipe(takeUntilDestroyed()) + .subscribe((data) => { + this.canAccessPremium = data; + }); + } + + subscribeToOrgKey() { + this.stateProvider.activeUserId$ + .pipe( + switchMap((userId) => (userId != null ? this.cryptoService.orgKeys$(userId) : NEVER)), + takeUntilDestroyed(), + ) + .subscribe((data: Record | null) => { + if (data) { + this.orgKey = data[this.cipher.organizationId as OrganizationId]; + } + }); + } +} diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2.component.html b/libs/vault/src/cipher-view/attachments/attachments-v2.component.html deleted file mode 100644 index acce6f2622f..00000000000 --- a/libs/vault/src/cipher-view/attachments/attachments-v2.component.html +++ /dev/null @@ -1,32 +0,0 @@ - - -

{{ "attachments" | i18n }}

-
- - -
-

- {{ attachment.fileName }} -

-
- {{ attachment.sizeName }} -
-
-
- - -
-
-
-
diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts b/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts deleted file mode 100644 index 6ea96bec497..00000000000 --- a/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component, Input } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { NEVER, switchMap } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; -import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { StateProvider } from "@bitwarden/common/platform/state"; -import { OrganizationId } from "@bitwarden/common/types/guid"; -import { OrgKey } from "@bitwarden/common/types/key"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { - ToastService, - ItemModule, - IconButtonModule, - SectionComponent, - SectionHeaderComponent, - TypographyModule, -} from "@bitwarden/components"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -@Component({ - selector: "app-attachments-v2", - templateUrl: "attachments-v2.component.html", - standalone: true, - imports: [ - CommonModule, - JslibModule, - ItemModule, - IconButtonModule, - SectionComponent, - SectionHeaderComponent, - TypographyModule, - ], -}) -export class AttachmentsV2Component { - @Input() cipher: CipherView; - - canAccessPremium: boolean; - orgKey: OrgKey; - private passwordReprompted = false; - - constructor( - private passwordRepromptService: PasswordRepromptService, - private i18nService: I18nService, - private apiService: ApiService, - private fileDownloadService: FileDownloadService, - private cryptoService: CryptoService, - private billingAccountProfileStateService: BillingAccountProfileStateService, - private toastService: ToastService, - private stateProvider: StateProvider, - private encryptService: EncryptService, - ) { - this.subscribeToHasPremiumCheck(); - this.subscribeToOrgKey(); - } - - subscribeToHasPremiumCheck() { - this.billingAccountProfileStateService.hasPremiumFromAnySource$ - .pipe(takeUntilDestroyed()) - .subscribe((data) => { - this.canAccessPremium = data; - }); - } - - subscribeToOrgKey() { - this.stateProvider.activeUserId$ - .pipe( - switchMap((userId) => (userId != null ? this.cryptoService.orgKeys$(userId) : NEVER)), - takeUntilDestroyed(), - ) - .subscribe((data: Record | null) => { - if (data) { - this.orgKey = data[this.cipher.organizationId as OrganizationId]; - } - }); - } - - async downloadAttachment(attachment: any) { - this.passwordReprompted = - this.passwordReprompted || - (await this.passwordRepromptService.passwordRepromptCheck(this.cipher)); - if (!this.passwordReprompted) { - return; - } - const file = attachment as any; - - if (file.downloading) { - return; - } - - if (this.cipher.organizationId == null && !this.canAccessPremium) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("premiumRequired"), - message: this.i18nService.t("premiumRequiredDesc"), - }); - return; - } - - let url: string; - try { - const attachmentDownloadResponse = await this.apiService.getAttachmentData( - this.cipher.id, - attachment.id, - ); - url = attachmentDownloadResponse.url; - } catch (e) { - if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { - url = attachment.url; - } else if (e instanceof ErrorResponse) { - throw new Error((e as ErrorResponse).getSingleMessage()); - } else { - throw e; - } - } - - file.downloading = true; - const response = await fetch(new Request(url, { cache: "no-store" })); - if (response.status !== 200) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - file.downloading = false; - return; - } - - try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = attachment.key != null ? attachment.key : this.orgKey; - const decBuf = await this.encryptService.decryptToBytes(encBuf, key); - this.fileDownloadService.download({ - fileName: attachment.fileName, - blobData: decBuf, - }); - } catch (e) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - } - - file.downloading = false; - } -} diff --git a/libs/vault/src/cipher-view/cipher-view.component.html b/libs/vault/src/cipher-view/cipher-view.component.html index 575d80257ea..222ae52a5d8 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.html +++ b/libs/vault/src/cipher-view/cipher-view.component.html @@ -15,12 +15,13 @@ - + + - + diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index 4764b571473..c2b32f13fdf 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -18,7 +18,7 @@ import { PopupHeaderComponent } from "../../../../apps/browser/src/platform/popu import { PopupPageComponent } from "../../../../apps/browser/src/platform/popup/layout/popup-page.component"; import { AdditionalInformationComponent } from "./additional-information/additional-information.component"; -import { AttachmentsV2Component } from "./attachments/attachments-v2.component"; +import { AttachmentsV2ViewComponent } from "./attachments/attachments-v2-view.component"; import { CustomFieldV2Component } from "./custom-fields/custom-fields-v2.component"; import { ItemDetailsV2Component } from "./item-details/item-details-v2.component"; import { ItemHistoryV2Component } from "./item-history/item-history-v2.component"; @@ -36,7 +36,7 @@ import { ItemHistoryV2Component } from "./item-history/item-history-v2.component PopupFooterComponent, ItemDetailsV2Component, AdditionalInformationComponent, - AttachmentsV2Component, + AttachmentsV2ViewComponent, ItemHistoryV2Component, CustomFieldV2Component, ], diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html index 91a380f53d5..0adb8535fa3 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html @@ -4,25 +4,25 @@
-
+ -
+
- + -
+
diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts index e54e5996eb5..b5826d82edf 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts @@ -1,10 +1,13 @@ import { CommonModule } from "@angular/common"; -import { Component, Input } from "@angular/core"; +import { Component, Input, OnInit } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { FieldType, LinkedIdType, LoginLinkedId } from "@bitwarden/common/vault/enums"; +import { CipherType, FieldType, LinkedIdType } from "@bitwarden/common/vault/enums"; +import { CardView } from "@bitwarden/common/vault/models/view/card.view"; import { FieldView } from "@bitwarden/common/vault/models/view/field.view"; +import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; +import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { CardComponent, IconButtonModule, @@ -13,6 +16,7 @@ import { SectionComponent, SectionHeaderComponent, TypographyModule, + CheckboxModule, } from "@bitwarden/components"; @Component({ @@ -29,21 +33,36 @@ import { SectionComponent, SectionHeaderComponent, TypographyModule, + CheckboxModule, ], }) -export class CustomFieldV2Component { +export class CustomFieldV2Component implements OnInit { @Input() fields: FieldView[]; + @Input() cipherType: CipherType; fieldType = FieldType; + fieldOptions: any; constructor(private i18nService: I18nService) {} - getLinkedType(linkedId: LinkedIdType) { - if (linkedId === LoginLinkedId.Username) { - return this.i18nService.t("username"); - } + ngOnInit(): void { + this.fieldOptions = this.getLinkedFieldsOptionsForCipher(); + } - if (linkedId === LoginLinkedId.Password) { - return this.i18nService.t("password"); + getLinkedType(linkedId: LinkedIdType) { + const linkedType = this.fieldOptions.get(linkedId); + return this.i18nService.t(linkedType.i18nKey); + } + + private getLinkedFieldsOptionsForCipher() { + switch (this.cipherType) { + case CipherType.Login: + return LoginView.prototype.linkedFieldOptions; + case CipherType.Card: + return CardView.prototype.linkedFieldOptions; + case CipherType.Identity: + return IdentityView.prototype.linkedFieldOptions; + default: + return null; } } } diff --git a/libs/vault/src/cipher-view/index.ts b/libs/vault/src/cipher-view/index.ts index 8231f5c1611..ec3c5235077 100644 --- a/libs/vault/src/cipher-view/index.ts +++ b/libs/vault/src/cipher-view/index.ts @@ -1 +1,2 @@ export * from "./cipher-view.component"; +export { CipherAttachmentsComponent } from "../cipher-form/components/attachments/cipher-attachments.component"; diff --git a/libs/vault/src/cipher-view/item-details/item-details-v2.component.html b/libs/vault/src/cipher-view/item-details/item-details-v2.component.html index 6941c25c2e3..77042755f88 100644 --- a/libs/vault/src/cipher-view/item-details/item-details-v2.component.html +++ b/libs/vault/src/cipher-view/item-details/item-details-v2.component.html @@ -3,36 +3,77 @@

{{ "itemDetails" | i18n }}

-
+
- +
-
-

- {{ "ownerYou" | i18n }} -

-

+

  • - {{ organization.name }} -

    -
      -

      - {{ collection.name }} -

      -
    -

    - {{ folder.name }} -

    -
  • + + + +
  • +
      +
    • + + +
    • +
    +
  • +
  • + + +
  • + diff --git a/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts b/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts index f4f60dc6f54..0694283b04f 100644 --- a/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts +++ b/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts @@ -13,6 +13,8 @@ import { TypographyModule, } from "@bitwarden/components"; +import { OrgIconDirective } from "../../components/org-icon.directive"; + @Component({ selector: "app-item-details-v2", templateUrl: "item-details-v2.component.html", @@ -24,6 +26,7 @@ import { SectionComponent, SectionHeaderComponent, TypographyModule, + OrgIconDirective, ], }) export class ItemDetailsV2Component { diff --git a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html index c1e11b9e58b..0515a4b24bc 100644 --- a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html +++ b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html @@ -11,7 +11,11 @@ {{ "dateCreated" | i18n }}: {{ cipher.creationDate | date: "medium" }}

    -

    +

    {{ "datePasswordUpdated" | i18n }}: {{ cipher.passwordRevisionDisplayDate | date: "medium" }}

    @@ -20,6 +24,7 @@ class="tw-font-bold tw-no-underline" routerLink="/cipher-password-history" [queryParams]="{ cipherId: cipher.id }" + bitTypography="body2" > {{ "passwordHistory" | i18n }} diff --git a/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts b/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts index 830e37da61e..539c3a1c369 100644 --- a/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts +++ b/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts @@ -3,6 +3,7 @@ import { Component, Input } from "@angular/core"; import { RouterModule } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CardComponent, @@ -27,4 +28,8 @@ import { }) export class ItemHistoryV2Component { @Input() cipher: CipherView; + + get isLogin() { + return this.cipher.type === CipherType.Login; + } } diff --git a/libs/vault/src/components/assign-collections.component.html b/libs/vault/src/components/assign-collections.component.html index 5663a9e817b..d68799eec6d 100644 --- a/libs/vault/src/components/assign-collections.component.html +++ b/libs/vault/src/components/assign-collections.component.html @@ -1,7 +1,14 @@ -

    {{ "bulkCollectionAssignmentDialogDescription" | i18n }}

    +

    + {{ + (personalItemsCount === 1 + ? "bulkCollectionAssignmentDialogDescriptionSingular" + : "bulkCollectionAssignmentDialogDescriptionPlural" + ) | i18n + }} +

    -
      +
      • {{ "bulkCollectionAssignmentWarning" | i18n: totalItemCount : readonlyItemCount }} @@ -15,7 +22,7 @@

      - + {{ "moveToOrganization" | i18n }}
      - + {{ "selectCollectionsToAssign" | i18n }} { this.formGroup.patchValue({ selectedOrg: orgs[0].id }); this.setFormValidators(); + + // Disable the org selector if there is only one organization + if (orgs.length === 1) { + this.formGroup.controls.selectedOrg.disable(); + } }); } }), ); protected transferWarningText = (orgName: string, itemsCount: number) => { - const pluralizedItems = this.pluralizePipe.transform(itemsCount, "item", "items"); - return orgName - ? this.i18nService.t("personalItemsWithOrgTransferWarning", pluralizedItems, orgName) - : this.i18nService.t("personalItemsTransferWarning", pluralizedItems); + const haveOrgName = !!orgName; + + if (itemsCount > 1 && haveOrgName) { + return this.i18nService.t("personalItemsWithOrgTransferWarningPlural", itemsCount, orgName); + } + if (itemsCount > 1 && !haveOrgName) { + return this.i18nService.t("personalItemsTransferWarningPlural", itemsCount); + } + if (itemsCount === 1 && haveOrgName) { + return this.i18nService.t("personalItemWithOrgTransferWarningSingular", orgName); + } + return this.i18nService.t("personalItemTransferWarningSingular"); }; private editableItems: CipherView[] = []; // Get the selected organization ID. If the user has not selected an organization from the form, // fallback to use the organization ID from the params. private get selectedOrgId(): OrganizationId { - return this.formGroup.value.selectedOrg || this.params.organizationId; + return this.formGroup.getRawValue().selectedOrg || this.params.organizationId; } private destroy$ = new Subject(); @@ -150,7 +162,6 @@ export class AssignCollectionsComponent implements OnInit { private organizationService: OrganizationService, private collectionService: CollectionService, private formBuilder: FormBuilder, - private pluralizePipe: PluralizePipe, private toastService: ToastService, ) {} @@ -411,7 +422,7 @@ export class AssignCollectionsComponent implements OnInit { variant: "success", title: null, message: this.i18nService.t( - "movedItemsToOrg", + shareableCiphers.length === 1 ? "itemMovedToOrg" : "itemsMovedToOrg", this.orgName ?? this.i18nService.t("organization"), ), }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.html b/libs/vault/src/components/download-attachment/download-attachment.component.html similarity index 100% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.html rename to libs/vault/src/components/download-attachment/download-attachment.component.html diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.spec.ts b/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts similarity index 95% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.spec.ts rename to libs/vault/src/components/download-attachment/download-attachment.component.spec.ts index 45c9e7fb377..39a6e6bc2fe 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.spec.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts @@ -14,8 +14,9 @@ import { StateProvider } from "@bitwarden/common/platform/state"; import { CipherType } from "@bitwarden/common/vault/enums"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { ToastService } from "@bitwarden/components"; -import { ToastService } from "../../../../../../../../../../libs/components/src/toast"; +import { PasswordRepromptService } from "../../services/password-reprompt.service"; import { DownloadAttachmentComponent } from "./download-attachment.component"; @@ -65,6 +66,7 @@ describe("DownloadAttachmentComponent", () => { { provide: ToastService, useValue: { showToast } }, { provide: ApiService, useValue: { getAttachmentData } }, { provide: FileDownloadService, useValue: { download } }, + { provide: PasswordRepromptService, useValue: mock() }, ], }).compileComponents(); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.ts b/libs/vault/src/components/download-attachment/download-attachment.component.ts similarity index 87% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.ts rename to libs/vault/src/components/download-attachment/download-attachment.component.ts index 528695eab45..3e00207c006 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.ts @@ -18,6 +18,8 @@ import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.v import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { AsyncActionsModule, IconButtonModule, ToastService } from "@bitwarden/components"; +import { PasswordRepromptService } from "../../services/password-reprompt.service"; + @Component({ standalone: true, selector: "app-download-attachment", @@ -31,9 +33,14 @@ export class DownloadAttachmentComponent { /** The cipher associated with the attachment */ @Input({ required: true }) cipher: CipherView; + // When in view mode, we will want to check for the master password reprompt + @Input() checkPwReprompt?: boolean = false; + /** The organization key if the cipher is associated with one */ private orgKey: OrgKey | null = null; + private passwordReprompted = false; + constructor( private i18nService: I18nService, private apiService: ApiService, @@ -42,6 +49,7 @@ export class DownloadAttachmentComponent { private encryptService: EncryptService, private stateProvider: StateProvider, private cryptoService: CryptoService, + private passwordRepromptService: PasswordRepromptService, ) { this.stateProvider.activeUserId$ .pipe( @@ -57,6 +65,15 @@ export class DownloadAttachmentComponent { /** Download the attachment */ download = async () => { + if (this.checkPwReprompt) { + this.passwordReprompted = + this.passwordReprompted || + (await this.passwordRepromptService.passwordRepromptCheck(this.cipher)); + if (!this.passwordReprompted) { + return; + } + } + let url: string; try { diff --git a/libs/vault/src/components/org-icon.directive.ts b/libs/vault/src/components/org-icon.directive.ts new file mode 100644 index 00000000000..1f0af29c832 --- /dev/null +++ b/libs/vault/src/components/org-icon.directive.ts @@ -0,0 +1,50 @@ +import { Directive, ElementRef, HostBinding, Input, Renderer2 } from "@angular/core"; + +import { ProductTierType } from "@bitwarden/common/billing/enums"; + +export type OrgIconSize = "default" | "small" | "large"; + +@Directive({ + standalone: true, + selector: "[appOrgIcon]", +}) +export class OrgIconDirective { + @Input({ required: true }) tierType: ProductTierType; + @Input() size?: OrgIconSize = "default"; + + constructor( + private el: ElementRef, + private renderer: Renderer2, + ) { + this.renderer.setAttribute(this.el.nativeElement, "aria-hidden", "true"); + } + + get iconSize(): "bwi-sm" | "bwi-lg" | "" { + switch (this.size) { + case "small": + return "bwi-sm"; + case "large": + return "bwi-lg"; + default: + return ""; + } + } + + get orgIcon(): string { + switch (this.tierType) { + case ProductTierType.Free: + case ProductTierType.Families: + return "bwi-family"; + case ProductTierType.Teams: + case ProductTierType.Enterprise: + case ProductTierType.TeamsStarter: + return "bwi-business"; + default: + return ""; + } + } + + @HostBinding("class") get classList() { + return ["bwi", this.iconSize, this.orgIcon]; + } +} diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts index 5dee70ea46f..e9417495a76 100644 --- a/libs/vault/src/index.ts +++ b/libs/vault/src/index.ts @@ -1,6 +1,7 @@ export { PasswordRepromptService } from "./services/password-reprompt.service"; export { CopyCipherFieldService, CopyAction } from "./services/copy-cipher-field.service"; export { CopyCipherFieldDirective } from "./components/copy-cipher-field.directive"; +export { OrgIconDirective } from "./components/org-icon.directive"; export * from "./cipher-view"; export * from "./cipher-form"; @@ -9,3 +10,5 @@ export { CollectionAssignmentParams, CollectionAssignmentResult, } from "./components/assign-collections.component"; + +export { DownloadAttachmentComponent } from "./components/download-attachment/download-attachment.component"; diff --git a/package-lock.json b/package-lock.json index ef366f224d5..0b4f18f2421 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,7 +66,7 @@ "qrious": "4.0.2", "rxjs": "7.8.1", "tabbable": "6.2.0", - "tldts": "6.1.29", + "tldts": "6.1.34", "utf-8-validate": "6.0.4", "zone.js": "0.13.3", "zxcvbn": "4.4.2" @@ -130,7 +130,7 @@ "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "6.10.0", - "electron": "31.2.0", + "electron": "31.2.1", "electron-builder": "24.13.3", "electron-log": "5.0.1", "electron-reload": "2.0.0-alpha.1", @@ -169,7 +169,7 @@ "react-dom": "18.3.1", "regedit": "^3.0.3", "remark-gfm": "3.0.1", - "rimraf": "5.0.8", + "rimraf": "6.0.1", "sass": "1.74.1", "sass-loader": "14.2.1", "storybook": "7.6.19", @@ -226,7 +226,7 @@ "papaparse": "5.4.1", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "tldts": "6.1.29", + "tldts": "6.1.34", "zxcvbn": "4.4.2" }, "bin": { @@ -18443,9 +18443,9 @@ } }, "node_modules/electron": { - "version": "31.2.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-31.2.0.tgz", - "integrity": "sha512-5w+kjOsGiTXytPSErBPNp/3znnuEMKc42RD41MqRoQkiYaR8x/Le2+qWk1cL60UwE/67oeKnOHnnol8xEuldGg==", + "version": "31.2.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-31.2.1.tgz", + "integrity": "sha512-g3CLKjl4yuXt6VWm/KpgEjYYhFiCl19RgUn8lOC8zV/56ZXAS3+mqV4wWzicE/7vSYXs6GRO7vkYRwrwhX3Gaw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -31496,6 +31496,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/pacote": { "version": "15.2.0", "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz", @@ -34534,19 +34541,106 @@ "license": "MIT" }, "node_modules/rimraf": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.8.tgz", - "integrity": "sha512-XSh0V2/yNhDEi8HwdIefD8MLgs4LQXPag/nEJWs3YUc3Upn+UHa1GyIkEg9xSSNt7HnkO5FjTvmcRzgf+8UZuw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", "dev": true, "license": "ISC", "dependencies": { - "glob": "^10.3.7" + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" }, "engines": { - "node": ">=18" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/jackspeak": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/rimraf/node_modules/lru-cache": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -37312,21 +37406,21 @@ "dev": true }, "node_modules/tldts": { - "version": "6.1.29", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.29.tgz", - "integrity": "sha512-6VgFZeuDsC6hrAP+H18CIofrXbA1I7yHsHcMutwK39bEc2fmXrtsLFshV4bg5vza4xiUP4zyAWr9C48KiyxZVA==", + "version": "6.1.34", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.34.tgz", + "integrity": "sha512-ErJIL8DMj1CLBER2aFrjI3IfhtuJD/jEYJA/iQg9wW8dIEPXNl4zcI/SGUihMsM/lP/Jyd8c2ETv6Cwtnk0/RQ==", "license": "MIT", "dependencies": { - "tldts-core": "^6.1.29" + "tldts-core": "^6.1.34" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.29", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.29.tgz", - "integrity": "sha512-ZhgwrF9P697hrsO8PZ4dFL8UZLLmczYcFwiknsPEk81BTC0xauqQfepPefIfS/YK2z2VVRQmyg0hZujShTlH7A==", + "version": "6.1.34", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.34.tgz", + "integrity": "sha512-Hb/jAm14h5x5+gO5Cv5wabKO0pbLlRoryvCC9v0t8OleZ4vXEKego7Mq1id/X1C8Vw1E0QCCQzGdWHkKFtxFrQ==", "license": "MIT" }, "node_modules/tmp": { @@ -39650,6 +39744,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.9.tgz", + "integrity": "sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "14 >=14.20 || 16 >=16.20 || >=18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.2.1.tgz", diff --git a/package.json b/package.json index ed8ebcef2f6..97b11715bd9 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "6.10.0", - "electron": "31.2.0", + "electron": "31.2.1", "electron-builder": "24.13.3", "electron-log": "5.0.1", "electron-reload": "2.0.0-alpha.1", @@ -131,7 +131,7 @@ "react-dom": "18.3.1", "regedit": "^3.0.3", "remark-gfm": "3.0.1", - "rimraf": "5.0.8", + "rimraf": "6.0.1", "sass": "1.74.1", "sass-loader": "14.2.1", "storybook": "7.6.19", @@ -202,7 +202,7 @@ "qrious": "4.0.2", "rxjs": "7.8.1", "tabbable": "6.2.0", - "tldts": "6.1.29", + "tldts": "6.1.34", "utf-8-validate": "6.0.4", "zone.js": "0.13.3", "zxcvbn": "4.4.2" diff --git a/tailwind.config.js b/tailwind.config.js index 50d82bf7d83..637c28a54ea 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -7,6 +7,7 @@ config.content = [ "./libs/auth/src/**/*.{html,ts,mdx}", "./libs/billing/src/**/*.{html,ts,mdx}", "./libs/platform/src/**/*.{html,ts,mdx}", + "./libs/tools/send/send-ui/src/*.{html,ts,mdx}", "./libs/vault/src/**/*.{html,ts,mdx}", "./apps/web/src/**/*.{html,ts,mdx}", "./bitwarden_license/bit-web/src/**/*.{html,ts,mdx}", diff --git a/tsconfig.json b/tsconfig.json index 89282287da2..79edf0da2e1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -49,6 +49,7 @@ "apps/web/src/**/*", "apps/browser/src/**/*", "libs/*/src/**/*", + "libs/tools/send/**/src/**/*", "bitwarden_license/bit-web/src/**/*", "bitwarden_license/bit-common/src/**/*" ],