From b2f79a15126bfb3de393930574718134c7ceaa7a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Aug 2022 11:54:24 -0700 Subject: [PATCH 01/16] Bumped desktop version to 2022.8.1 (#3284) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 2962e03904a..c0b306d8373 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2022.8.0", + "version": "2022.8.1", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 09739ef6c53..a491ab12ecd 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2022.8.0", + "version": "2022.8.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2022.8.0", + "version": "2022.8.1", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-native": "file:../desktop_native" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index ccff839c446..045762aa2d1 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2022.8.0", + "version": "2022.8.1", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index d1aa4525845..a4181f92245 100644 --- a/package-lock.json +++ b/package-lock.json @@ -221,7 +221,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2022.8.0", + "version": "2022.8.1", "hasInstallScript": true, "license": "GPL-3.0" }, From c0a6db7d00e54bd4ffa37392365d0c6722893c38 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Aug 2022 14:11:38 -0700 Subject: [PATCH 02/16] Bumped web version to 2022.8.1 (#3287) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- apps/web/package.json | 2 +- package-lock.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 33d70628f12..39087a2e91f 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2022.8.0", + "version": "2022.8.1", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/package-lock.json b/package-lock.json index a4181f92245..c8b5aa61649 100644 --- a/package-lock.json +++ b/package-lock.json @@ -235,7 +235,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2022.8.0" + "version": "2022.8.1" }, "libs/angular": { "name": "@bitwarden/angular", From 144783d4a4bb909f206e5721bb924ec5ef62df0b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 12 Aug 2022 11:29:28 +0200 Subject: [PATCH 03/16] Autosync the updated translations (#3289) Co-authored-by: github-actions <> --- apps/browser/src/_locales/ar/messages.json | 21 +++++++++ apps/browser/src/_locales/az/messages.json | 21 +++++++++ apps/browser/src/_locales/be/messages.json | 21 +++++++++ apps/browser/src/_locales/bg/messages.json | 21 +++++++++ apps/browser/src/_locales/bn/messages.json | 21 +++++++++ apps/browser/src/_locales/bs/messages.json | 21 +++++++++ apps/browser/src/_locales/ca/messages.json | 21 +++++++++ apps/browser/src/_locales/cs/messages.json | 21 +++++++++ apps/browser/src/_locales/da/messages.json | 21 +++++++++ apps/browser/src/_locales/de/messages.json | 21 +++++++++ apps/browser/src/_locales/el/messages.json | 21 +++++++++ apps/browser/src/_locales/en_GB/messages.json | 43 ++++++++++++++----- apps/browser/src/_locales/en_IN/messages.json | 21 +++++++++ apps/browser/src/_locales/es/messages.json | 23 +++++++++- apps/browser/src/_locales/et/messages.json | 41 +++++++++++++----- apps/browser/src/_locales/eu/messages.json | 25 ++++++++++- apps/browser/src/_locales/fa/messages.json | 21 +++++++++ apps/browser/src/_locales/fi/messages.json | 21 +++++++++ apps/browser/src/_locales/fil/messages.json | 21 +++++++++ apps/browser/src/_locales/fr/messages.json | 29 +++++++++++-- apps/browser/src/_locales/he/messages.json | 21 +++++++++ apps/browser/src/_locales/hi/messages.json | 21 +++++++++ apps/browser/src/_locales/hr/messages.json | 21 +++++++++ apps/browser/src/_locales/hu/messages.json | 21 +++++++++ apps/browser/src/_locales/id/messages.json | 21 +++++++++ apps/browser/src/_locales/it/messages.json | 21 +++++++++ apps/browser/src/_locales/ja/messages.json | 21 +++++++++ apps/browser/src/_locales/ka/messages.json | 21 +++++++++ apps/browser/src/_locales/km/messages.json | 21 +++++++++ apps/browser/src/_locales/kn/messages.json | 21 +++++++++ apps/browser/src/_locales/ko/messages.json | 21 +++++++++ apps/browser/src/_locales/lt/messages.json | 21 +++++++++ apps/browser/src/_locales/lv/messages.json | 21 +++++++++ apps/browser/src/_locales/ml/messages.json | 21 +++++++++ apps/browser/src/_locales/nb/messages.json | 21 +++++++++ apps/browser/src/_locales/nl/messages.json | 21 +++++++++ apps/browser/src/_locales/nn/messages.json | 21 +++++++++ apps/browser/src/_locales/pl/messages.json | 21 +++++++++ apps/browser/src/_locales/pt_BR/messages.json | 21 +++++++++ apps/browser/src/_locales/pt_PT/messages.json | 21 +++++++++ apps/browser/src/_locales/ro/messages.json | 21 +++++++++ apps/browser/src/_locales/ru/messages.json | 21 +++++++++ apps/browser/src/_locales/si/messages.json | 21 +++++++++ apps/browser/src/_locales/sk/messages.json | 21 +++++++++ apps/browser/src/_locales/sl/messages.json | 21 +++++++++ apps/browser/src/_locales/sr/messages.json | 37 ++++++++++++---- apps/browser/src/_locales/sv/messages.json | 21 +++++++++ apps/browser/src/_locales/th/messages.json | 21 +++++++++ apps/browser/src/_locales/tr/messages.json | 21 +++++++++ apps/browser/src/_locales/uk/messages.json | 21 +++++++++ apps/browser/src/_locales/vi/messages.json | 21 +++++++++ apps/browser/src/_locales/zh_CN/messages.json | 21 +++++++++ apps/browser/src/_locales/zh_TW/messages.json | 21 +++++++++ 53 files changed, 1149 insertions(+), 36 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 20dfcf4357d..3fd3fe8875d 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 6b9e522360e..914d56862c3 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Açar bağlayıcı xətası: Açar Bağlayıcının mövcud olduğuna və düzgün işlədiyinə əmin olun." }, + "premiumSubcriptionRequired": { + "message": "Premium abunəlik tələb olunur" + }, "organizationIsDisabled": { "message": "Təşkilat sıradan çıxarıldı." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "$DOMAIN$ domeninə giriş edilir", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Tənzimləmələrə düzəliş edildi" + }, + "environmentEditedClick": { + "message": "Bura klikləyin" + }, + "environmentEditedReset": { + "message": "ön konfiqurasiyalı tənzimləmələri sıfırlamaq üçün" } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 9304c0f1ab8..f8ba6238b8f 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 38ddc737ebc..f7723538001 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Грешка с конектора за ключове: уверете се, че конекторът за ключове е наличен и работи правилно." }, + "premiumSubcriptionRequired": { + "message": "Изисква се платен абонамент" + }, "organizationIsDisabled": { "message": "Организацията е изключена." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Мир" + }, + "loggingInTo": { + "message": "Вписване в $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Настройките баха променени" + }, + "environmentEditedClick": { + "message": "Щракнете тук" + }, + "environmentEditedReset": { + "message": "за да върнете предварително зададените настройки" } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index ffffd1cfecd..7e702fcf3e8 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index cd82aad892a..0bbea26f561 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 4220043f3d6..9170b3978f0 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Error del connector de claus: assegureu-vos que el connector de claus està disponible i funcionant correctament." }, + "premiumSubcriptionRequired": { + "message": "Cal una subscripció premium" + }, "organizationIsDisabled": { "message": "L'organització està inhabilitada." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Inici de sessió a $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "La configuració s'ha editat" + }, + "environmentEditedClick": { + "message": "Feu clic ací" + }, + "environmentEditedReset": { + "message": "per restablir els paràmetres preconfigurats" } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 417239cec89..fd34a760f92 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index d11d8760e50..7191c492e4b 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector-fejl: Sørg for, at Key Connector er tilgængelig og fungerer korrekt." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organisationen er deaktiveret." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 08b90515e2e..c85a71848b2 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector Fehler: Stelle sicher, dass der Key Connector verfügbar ist und einwandfrei funktioniert." }, + "premiumSubcriptionRequired": { + "message": "Premium-Abonnement erforderlich" + }, "organizationIsDisabled": { "message": "Organisation ist deaktiviert." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Anmelden bei $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Einstellungen wurden bearbeitet" + }, + "environmentEditedClick": { + "message": "Hier klicken" + }, + "environmentEditedReset": { + "message": "um auf vorkonfigurierte Einstellungen zurückzusetzen" } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 9bb9ca4b227..4f879ede07a 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Σφάλμα Key Connector: βεβαιωθείτε ότι το Key Connector είναι διαθέσιμο και λειτουργεί σωστά." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 69109391a21..91e65e5e317 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -574,7 +574,7 @@ "message": "Ask to add login" }, "addLoginNotificationDesc": { - "message": "The \"add login notification\" automatically prompts you to save new logins to your vault whenever you log into them for the first time." + "message": "Ask to add an item if one isn't found in your vault." }, "showCardsCurrentTab": { "message": "Show cards on Tab page" @@ -642,7 +642,7 @@ "description": "Light color" }, "solarizedDark": { - "message": "Solarized Dark", + "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, "exportVault": { @@ -674,7 +674,7 @@ "message": "Shared" }, "learnOrg": { - "message": "Learn about Organisations" + "message": "Learn about organisations" }, "learnOrgConfirmation": { "message": "Bitwarden allows you to share your vault items with others by using an organisation. Would you like to visit the bitwarden.com website to learn more?" @@ -813,7 +813,7 @@ "message": "Copy TOTP automatically" }, "disableAutoTotpCopyDesc": { - "message": "If your login has an authenticator key attached to it, the TOTP verification code is automatically copied to your clipboard whenever you auto-fill the login." + "message": "If a login has an authenticator key, copy the TOTP verification code to your clip-board when you auto-fill the login." }, "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" @@ -954,10 +954,10 @@ "message": "The environment URLs have been saved." }, "enableAutoFillOnPageLoad": { - "message": "Enable Auto-fill on Page Load" + "message": "Auto-fill on page load" }, "enableAutoFillOnPageLoadDesc": { - "message": "If a login form is detected, automatically perform an auto-fill when the web page loads." + "message": "If a login form is detected, auto-fill when the web page loads." }, "experimentalFeature": { "message": "This is currently an experimental feature. Use at your own risk." @@ -966,10 +966,10 @@ "message": "Default autofill setting for login items" }, "defaultAutoFillOnPageLoadDesc": { - "message": "After enabling Auto-fill on Page Load, you can enable or disable the feature for individual login items. This is the default setting for login items that are not separately configured." + "message": "You can turn off auto-fill on page load for individual login items from the item's Edit view." }, "itemAutoFillOnPageLoad": { - "message": "Auto-fill on Page Load (if enabled in Options)" + "message": "Auto-fill on page load (if enabled in Options)" }, "autoFillOnPageLoadUseDefault": { "message": "Use default setting" @@ -1252,7 +1252,7 @@ "description": "Domain name. Ex. website.com" }, "domainName": { - "message": "Domain Name", + "message": "Domain name", "description": "Domain name. Ex. website.com" }, "host": { @@ -1514,7 +1514,7 @@ "message": "Start the Bitwarden Desktop application" }, "startDesktopDesc": { - "message": "The Bitwarden Desktop application needs to be started before this function can be used." + "message": "The Bitwarden Desktop application needs to be started before unlock with biometrics can be used." }, "errorEnableBiometricTitle": { "message": "Unable to enable biometrics" @@ -1565,7 +1565,7 @@ "message": "An organisation policy is affecting your ownership options." }, "excludedDomains": { - "message": "Excluded Domains" + "message": "Excluded domains" }, "excludedDomainsDesc": { "message": "Bitwarden will not ask to save login details for these domains. You must refresh the page for changes to take effect." @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organisation is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 72ce5d51709..acdde9117c4 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index c0c94db6e49..8203f36fe4a 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -430,7 +430,7 @@ "message": "Se requiere volver a teclear la contraseña maestra." }, "masterPasswordMinlength": { - "message": "Master password must be at least 8 characters long." + "message": "La contraseña maestra debe tener al menos 8 caracteres." }, "masterPassDoesntMatch": { "message": "La confirmación de contraseña maestra no coincide." @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Error en el conector de claves: asegúrate de que el conector de claves está disponible y que funciona correctamente." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "La organización está desactivada." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Iniciando sesión en $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Se han editado los ajustes" + }, + "environmentEditedClick": { + "message": "Haga click aquí" + }, + "environmentEditedReset": { + "message": "para restablecer a los ajustes por defecto" } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 83acb444300..4b99f7da66b 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -603,10 +603,10 @@ "message": "Jah, salvesta see" }, "enableChangedPasswordNotification": { - "message": "Ask to update existing login" + "message": "Paku olemasolevate andmete uuendamist" }, "changedPasswordNotificationDesc": { - "message": "Ask to update a login's password when a change is detected on a website." + "message": "Kui veebilehel tuvastatakse olemasolevate andmete muutmine, siis pakutakse nende andmete uuendamist Bitwardenis." }, "notificationChangeDesc": { "message": "Soovid seda parooli ka Bitwardenis uuendada?" @@ -615,10 +615,10 @@ "message": "Jah, uuenda" }, "enableContextMenuItem": { - "message": "Show context menu options" + "message": "Kuva parema kliki menüü valikud" }, "contextMenuItemDesc": { - "message": "Use a secondary click to access password generation and matching logins for the website. " + "message": "Võimaldab parema kliki menüüs kaustada Bitwardeni valikuid, nt kontoandmete täitmist või parooli genereerimist. " }, "defaultUriMatchDetection": { "message": "Vaike URI sobivuse tuvastamine", @@ -810,13 +810,13 @@ "message": "Uuendamine lõpetatud" }, "enableAutoTotpCopy": { - "message": "Copy TOTP automatically" + "message": "TOTP automaatne kopeerimine" }, "disableAutoTotpCopyDesc": { "message": "Kui sinu sisselogimise andmetele on juurde lisatud autentimise võti, kopeeritakse TOTP kood automaatse täitmise kasutamisel lõikelauale." }, "enableAutoBiometricsPrompt": { - "message": "Ask for biometrics on launch" + "message": "Küsi avamisel biomeetriat" }, "premiumRequired": { "message": "Vajalik on Premium versioon" @@ -1037,16 +1037,16 @@ "message": "Kasutatav brauser ei suuda selles aknas U2F päringuid töödelda. Kas avan uue akna, et saaksid U2F abil sisse logida?" }, "enableFavicon": { - "message": "Show website icons" + "message": "Kuva veebilehtede ikoone" }, "faviconDesc": { - "message": "Show a recognizable image next to each login." + "message": "Kuvab iga kirje kõrval lehekülje ikooni." }, "enableBadgeCounter": { - "message": "Show badge counter" + "message": "Kuva kirjete arvu" }, "badgeCounterDesc": { - "message": "Indicate how many logins you have for the current web page." + "message": "Kuvab numbrina konkreetsel veebilehel olevate kirjete arvu." }, "cardholderName": { "message": "Kaardiomaniku nimi" @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connectori viga: veendu, et Key Connector on saadaval ja töötab korrektselt." }, + "premiumSubcriptionRequired": { + "message": "Vajalik on Premium versioon" + }, "organizationIsDisabled": { "message": "Organisatsiooni ligipääs on keelatud." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Sisselogimine läbi $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Seaded on uuendatud" + }, + "environmentEditedClick": { + "message": "Kliki siia," + }, + "environmentEditedReset": { + "message": "et taastada eelseadistatud seaded" } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 344f2dba481..83677b7140e 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -708,7 +708,7 @@ "message": "Autentifikazio-gakoa (TOTP)" }, "verificationCodeTotp": { - "message": "Egiaztatze kodea" + "message": "Egiaztatze-kodea (TOTP)" }, "copyVerificationCode": { "message": "Kopiatu egiaztatze-kodea" @@ -810,7 +810,7 @@ "message": "Eguneratzea eginda" }, "enableAutoTotpCopy": { - "message": "Kopiatu TOTO automatikoki" + "message": "Kopiatu TOTP automatikoki" }, "disableAutoTotpCopyDesc": { "message": "Saio hasiera batek autentifikazio-gakoa badu, TOTP egiaztatze-kodea arbelean automatikoki kopiatuko da saio hasiera bat auto-betetzean." @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Errore bat gertatu da Key Connector-ekin: ziurtatu Key Connector erabilgarri dagoela eta behar bezala dabilela." }, + "premiumSubcriptionRequired": { + "message": "Premium harpidetza behar da" + }, "organizationIsDisabled": { "message": "Erakundea desgaituta dago." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "$DOMAIN$-en saioa hasten", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Ezarpenak editatu dira" + }, + "environmentEditedClick": { + "message": "Sakatu hemen" + }, + "environmentEditedReset": { + "message": "ezarpen lehenetsiak ezartzeko" } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index bee4354635f..cc60dac223d 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "خطای Key Connector: مطمئن شوید که Key Connector در دسترس است و به درستی کار می کند." }, + "premiumSubcriptionRequired": { + "message": "اشتراک پرمیوم نیاز است" + }, "organizationIsDisabled": { "message": "سازمان از کار افتاده است." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "میر" + }, + "loggingInTo": { + "message": "ورود به $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "تنظیمات ویرایش شده اند" + }, + "environmentEditedClick": { + "message": "اینجا کلیک کنید" + }, + "environmentEditedReset": { + "message": "برای بازنشانی به تنظیمات از پیش پیکربندی شده" } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 3622faaddc5..a91f2bcdb3c 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector -virhe: Varmista, että Key Connector on käytettävissä ja toimii oikein." }, + "premiumSubcriptionRequired": { + "message": "Premium-tilaus vaaditaan" + }, "organizationIsDisabled": { "message": "Organisaatio on poistettu käytöstä." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Kirjaudutaan palveluun $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Asetuksia on muokattu" + }, + "environmentEditedClick": { + "message": "Paina tästä" + }, + "environmentEditedReset": { + "message": "palauttaaksesi esimääritetyt asetukset" } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 50dd0b856b6..ccf34655e19 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index e73e0ba2405..292e448322b 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -424,13 +424,13 @@ "message": "Adresse e-mail invalide." }, "masterPasswordRequired": { - "message": "Master password is required." + "message": "Le mot de passe maître est requis." }, "confirmMasterPasswordRequired": { - "message": "Master password retype is required." + "message": "Le mot de passe maître doit être entré de nouveau." }, "masterPasswordMinlength": { - "message": "Master password must be at least 8 characters long." + "message": "Le mot de passe maître doit au moins contenir 8 caractères." }, "masterPassDoesntMatch": { "message": "La confirmation du mot de passe maître ne correspond pas." @@ -1968,13 +1968,34 @@ "ssoKeyConnectorError": { "message": "Erreur du connecteur de clé: veuillez vérifier que le connecteur de clé est disponible et qu'il fonctionne correctement." }, + "premiumSubcriptionRequired": { + "message": "Un abonnement Premium est requis" + }, "organizationIsDisabled": { - "message": "Organization is disabled." + "message": "L'organisation est désactivée." }, "disabledOrganizationFilterError": { "message": "Items in disabled Organizations cannot be accessed. Contact your Organization owner for assistance." }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Connexion à $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Les paramètres ont été modifiés" + }, + "environmentEditedClick": { + "message": "Cliquez ici" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index e7f4a06ecab..b053866ea04 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index a4390d6bb34..d0b03617407 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index c439763346f..8c73b4c13eb 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Pogreška konektora ključa: provjerite je li konektor ključa dostupan i radi ispravno." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index f1a3d623c2a..6069fcdcb65 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Kulcs csatlakozó hiba: ellenőrizzük, hogy a kulcs csatlakozó rendelkezésre áll-e és megfelelően működik-e." }, + "premiumSubcriptionRequired": { + "message": "Prémium előfizetés szükséges" + }, "organizationIsDisabled": { "message": "A szervezet letiltásra került." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Bejelentkezés $DOMAIN$ területre", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "A beállítások szerkesztésre kerültek." + }, + "environmentEditedClick": { + "message": "Kattintás ide" + }, + "environmentEditedReset": { + "message": "az előre konfigurált beállítások visszaállításához." } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index b22718d1d35..f067df0dc78 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 14014d8d9f8..d01b13ec3f8 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Errore Key Connector: assicurarsi che il Key Connector sia disponibile e correttamente funzionante." }, + "premiumSubcriptionRequired": { + "message": "È richiesto un abbonamento Premium" + }, "organizationIsDisabled": { "message": "L'organizzazione è disabilitata." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Accedo a $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Le impostazioni sono state cambiate" + }, + "environmentEditedClick": { + "message": "Clicca qui" + }, + "environmentEditedReset": { + "message": "per ritornare alle impostazioni preconfigurate" } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index fe6416cb1a8..6117948f427 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "キーコネクターエラー: キーコネクターが使用可能で、正常に動作しているか確認してください。" }, + "premiumSubcriptionRequired": { + "message": "プレミアム版が必要です" + }, "organizationIsDisabled": { "message": "組織は無効です。" }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "$DOMAIN$ にログイン中", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "設定を更新しました" + }, + "environmentEditedClick": { + "message": "ここをクリック" + }, + "environmentEditedReset": { + "message": "すると初期設定に戻します" } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 088dc9c6c02..15acccb2591 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 20dfcf4357d..3fd3fe8875d 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 2fcbc02154d..7751a99cb3e 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 72fef9f434b..597484c5a28 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "키 커넥터 오류: 키 커넥터가 사용 가능한지 및 정상적으로 작동하고 있는지 확인해주세요." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 81e7c405398..5c154c2489b 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 48283562f2d..7bc2f416ad3 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector kļūda: jāpārliecinās, ka Key Connector ir pieejams un darbojas pareizi." }, + "premiumSubcriptionRequired": { + "message": "Nepieciešams Premium abonements" + }, "organizationIsDisabled": { "message": "Apvienība ir atspējota." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Pierakstās $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Iestatījumi ir izmainīti" + }, + "environmentEditedClick": { + "message": "Klikšķināt šeit" + }, + "environmentEditedReset": { + "message": "lai atiestatītu pirmsuzstādītos iestatījumus" } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 00bc3b18267..76447ecbd36 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 96b6d6c9860..52385e651b6 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector feil: Sjekk at Key Connector er tilgjengelig og fungerer." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index cfca91f764b..b51c3ea81ca 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key-connector fout: zorg ervoor dat Key-connector beschikbaar is en werkt." }, + "premiumSubcriptionRequired": { + "message": "Premium-abonnement vereist" + }, "organizationIsDisabled": { "message": "Organisatie is uitgeschakeld." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Inloggen op $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Instellingen zijn bijgewerkt" + }, + "environmentEditedClick": { + "message": "Klik hier" + }, + "environmentEditedReset": { + "message": "om terug te gaan naar vooraf geconfigureerde instellingen" } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 20dfcf4357d..3fd3fe8875d 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index ac9273793d3..dff14b6fbb1 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Błąd serwera Key Connector: upewnij się, że serwer Key Connector jest dostępny i działa poprawnie." }, + "premiumSubcriptionRequired": { + "message": "Wymagana jest subskrypcja Premium" + }, "organizationIsDisabled": { "message": "Organizacja jest wyłączona." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logowanie do $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Ustawienia zostały edytowane" + }, + "environmentEditedClick": { + "message": "Kliknij tutaj" + }, + "environmentEditedReset": { + "message": "aby zresetować do wstępnie skonfigurowanych ustawień" } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index e5218c685ba..c26f8e59f79 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Erro de Key Connector: certifique-se de que a Key Connector está disponível e funcionando corretamente." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 655fd385410..98385fcbeaf 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Subscrição premium necessária" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "A iniciar sessão em $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "As definições foram editadas" + }, + "environmentEditedClick": { + "message": "Clique aqui" + }, + "environmentEditedReset": { + "message": "para voltar às definições predefinidas" } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index dd849355cec..f467a240202 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Eroare de Conector Cheie: asigurați-vă că aveți conectorul Cheie disponibil și că funcționează corect." }, + "premiumSubcriptionRequired": { + "message": "Este necesar un abonament Premium" + }, "organizationIsDisabled": { "message": "Organizația este dezactivată." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Conectarea la $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Setările au fost editate" + }, + "environmentEditedClick": { + "message": "Faceți clic aici" + }, + "environmentEditedReset": { + "message": "pentru a restabili setările preconfigurate" } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index cbbac44abb6..34aeddd6294 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Ошибка соединителя ключей: убедитесь, что он доступен и работает корректно." }, + "premiumSubcriptionRequired": { + "message": "Требуется подписка Премиум" + }, "organizationIsDisabled": { "message": "Организация отключена." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Вход в $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Настройки были изменены" + }, + "environmentEditedClick": { + "message": "Нажмите здесь" + }, + "environmentEditedReset": { + "message": "для сброса к предварительно настроенным параметрам" } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index e060dbd55d5..5b5ae9544e0 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 325644a3066..5601fb90a4f 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Chyba Key Connector: uistite sa, že je Key Connector k dispozícii a funguje správne." }, + "premiumSubcriptionRequired": { + "message": "Vyžaduje sa Premiové predplatné" + }, "organizationIsDisabled": { "message": "Organizácia je vypnutá." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Prihlásenie do $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Nastavenia boli upravené" + }, + "environmentEditedClick": { + "message": "Kliknite sem" + }, + "environmentEditedReset": { + "message": "na obnovenie predvolených nastavení" } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 316286902b3..36b52f9dc4f 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index c356a542a5d..011a7bea482 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -35,10 +35,10 @@ "message": "Адреса е-поште" }, "masterPass": { - "message": "Главна Лозинка" + "message": "Главна лозинка" }, "masterPassDesc": { - "message": "Главна Лозинка је лозинка коју користите за приступ Вашем сефу. Врло је важно да је не заборавите. Не постоји начин да повратите лозинку у случају да је заборавите." + "message": "Главна лозинка је лозинка коју користите за приступ Вашем сефу. Врло је важно да је не заборавите. Не постоји начин да повратите лозинку у случају да је заборавите." }, "masterPassHintDesc": { "message": "Савет Главне Лозинке може да Вам помогне да се потсетите ако је заборавите." @@ -339,7 +339,7 @@ "message": "Ваш прегледач не подржава једноставно копирање у привремену меморију. Уместо тога копирајте га ручно." }, "verifyIdentity": { - "message": "Проверити идентитет" + "message": "Потврдите идентитет" }, "yourVaultIsLocked": { "message": "Сеф је закључан. Унесите главну лозинку за наставак." @@ -424,16 +424,16 @@ "message": "Неисправан имејл." }, "masterPasswordRequired": { - "message": "Главна Лозинка је неопходна." + "message": "Главна лозинка је неопходна." }, "confirmMasterPasswordRequired": { "message": "Поновно уписивање главне лозинке је неопходно." }, "masterPasswordMinlength": { - "message": "Главна Лозинка треба имати бар 8 знака." + "message": "Главна лозинка треба имати барем 8 карактера." }, "masterPassDoesntMatch": { - "message": "Потврђена Главна Лозинка се не подудара." + "message": "Потврда главне лозинке се не подудара." }, "newAccountCreated": { "message": "Ваш налог је креиран! Сада се можете пријавити." @@ -1343,7 +1343,7 @@ "description": "ex. A weak password. Scale: Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "Слаба Главна Лозинка" + "message": "Слаба главна лозинка" }, "weakMasterPasswordDesc": { "message": "Главна лозинка коју сте одабрали је слаба. Требали бисте користити јаку главну лозинку (или фразу лозинке) да бисте правилно заштитили свој налог. Да ли сте сигурни да желите да користите ову главну лозинку?" @@ -1880,7 +1880,7 @@ "message": "Уклони главну лозинку" }, "removedMasterPassword": { - "message": "Главна лозинка уклоњена." + "message": "Главна лозинка је уклоњена." }, "leaveOrganizationConfirmation": { "message": "Да ли сте сигурни да желите да напустите ову организацију?" @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector грешка: будите сигурни да је Key Connector доступан и да ради." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Организација је онемогућена." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index dce41ce8cce..70672f31728 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector-fel: säkerställ att Key Connector är tillgänglig och fungerar korrekt." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organisationen är inaktiverad." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index b7dd9a066d1..26c07d9168b 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Settings have been edited" + }, + "environmentEditedClick": { + "message": "Click here" + }, + "environmentEditedReset": { + "message": "to reset to pre-configured settings" } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 8b506f86013..eaf967c9c5c 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Anahtar Bağlayıcı hatası: Anahtar Bağlayıcının mevcut olduğundan ve doğru çalıştığından emin olun." }, + "premiumSubcriptionRequired": { + "message": "Premium abonelik gerekli" + }, "organizationIsDisabled": { "message": "Kuruluş devre dışı." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "$DOMAIN$ sitesine giriş yapılıyor", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Ayarlar düzenlendi" + }, + "environmentEditedClick": { + "message": "Buraya tıklayarak" + }, + "environmentEditedReset": { + "message": "ön tanımlı ayarları sıfırlayabilirsiniz" } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index fef0363aa87..396c29a1cb5 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Помилка Key Connector: переконайтеся, що Key Connector доступний та працює правильно." }, + "premiumSubcriptionRequired": { + "message": "Необхідна передплата преміум" + }, "organizationIsDisabled": { "message": "Організацію вимкнено." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Вхід до $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Налаштування змінено" + }, + "environmentEditedClick": { + "message": "Натисніть тут," + }, + "environmentEditedReset": { + "message": "щоб скинути налаштування" } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index d622de2968b..9a96d69fcd3 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector error: make sure Key Connector is available and working correctly." }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "Đang đăng nhập vào $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "Cài đặt đã được chỉnh sửa" + }, + "environmentEditedClick": { + "message": "Nhấn vào đây" + }, + "environmentEditedReset": { + "message": "để đặt lại cài đặt đã thiết đặt từ trước" } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 4f2b979b403..7590de95e87 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector 错误:请确保 Key Connector 可用且工作正常。" }, + "premiumSubcriptionRequired": { + "message": "需要高级版订阅" + }, "organizationIsDisabled": { "message": "组织已被禁用。" }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "正在登录到 $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "设置已编辑" + }, + "environmentEditedClick": { + "message": "点击此处" + }, + "environmentEditedReset": { + "message": "重置为预设设置" } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 8fe8654c19b..861b3137725 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -1968,6 +1968,9 @@ "ssoKeyConnectorError": { "message": "Key Connector 錯誤:請確保 Key Connector 可用且運作正常。" }, + "premiumSubcriptionRequired": { + "message": "需要進階版訂閲" + }, "organizationIsDisabled": { "message": "組織已停用。" }, @@ -1976,5 +1979,23 @@ }, "cardBrandMir": { "message": "Mir" + }, + "loggingInTo": { + "message": "正在登入至 $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "settingsEdited": { + "message": "設定已編輯" + }, + "environmentEditedClick": { + "message": "點選此處" + }, + "environmentEditedReset": { + "message": "重設為預設設定" } } From d8cb129abee7f6d0c2b4fcf7937e68b0affffa18 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 12 Aug 2022 11:32:09 +0200 Subject: [PATCH 04/16] Autosync the updated translations (#3290) Co-authored-by: github-actions <> --- apps/desktop/src/locales/af/messages.json | 17 +- apps/desktop/src/locales/ar/messages.json | 3 + apps/desktop/src/locales/az/messages.json | 3 + apps/desktop/src/locales/be/messages.json | 125 +- apps/desktop/src/locales/bg/messages.json | 3 + apps/desktop/src/locales/bn/messages.json | 3 + apps/desktop/src/locales/bs/messages.json | 3 + apps/desktop/src/locales/ca/messages.json | 3 + apps/desktop/src/locales/cs/messages.json | 3 + apps/desktop/src/locales/da/messages.json | 3 + apps/desktop/src/locales/de/messages.json | 3 + apps/desktop/src/locales/el/messages.json | 3 + apps/desktop/src/locales/en_GB/messages.json | 3 + apps/desktop/src/locales/en_IN/messages.json | 3 + apps/desktop/src/locales/eo/messages.json | 55 +- apps/desktop/src/locales/es/messages.json | 3 + apps/desktop/src/locales/et/messages.json | 29 +- apps/desktop/src/locales/eu/messages.json | 1185 +++++++++--------- apps/desktop/src/locales/fa/messages.json | 3 + apps/desktop/src/locales/fi/messages.json | 3 + apps/desktop/src/locales/fil/messages.json | 3 + apps/desktop/src/locales/fr/messages.json | 3 + apps/desktop/src/locales/he/messages.json | 91 +- apps/desktop/src/locales/hi/messages.json | 3 + apps/desktop/src/locales/hr/messages.json | 3 + apps/desktop/src/locales/hu/messages.json | 3 + apps/desktop/src/locales/id/messages.json | 3 + apps/desktop/src/locales/it/messages.json | 3 + apps/desktop/src/locales/ja/messages.json | 3 + apps/desktop/src/locales/ka/messages.json | 3 + apps/desktop/src/locales/km/messages.json | 3 + apps/desktop/src/locales/kn/messages.json | 3 + apps/desktop/src/locales/ko/messages.json | 3 + apps/desktop/src/locales/lv/messages.json | 3 + apps/desktop/src/locales/me/messages.json | 3 + apps/desktop/src/locales/ml/messages.json | 3 + apps/desktop/src/locales/nb/messages.json | 9 +- apps/desktop/src/locales/nl/messages.json | 3 + apps/desktop/src/locales/nn/messages.json | 3 + apps/desktop/src/locales/pl/messages.json | 3 + apps/desktop/src/locales/pt_BR/messages.json | 3 + apps/desktop/src/locales/pt_PT/messages.json | 3 + apps/desktop/src/locales/ro/messages.json | 3 + apps/desktop/src/locales/ru/messages.json | 3 + apps/desktop/src/locales/si/messages.json | 3 + apps/desktop/src/locales/sk/messages.json | 3 + apps/desktop/src/locales/sl/messages.json | 3 + apps/desktop/src/locales/sr/messages.json | 3 + apps/desktop/src/locales/sv/messages.json | 3 + apps/desktop/src/locales/th/messages.json | 3 + apps/desktop/src/locales/tr/messages.json | 3 + apps/desktop/src/locales/uk/messages.json | 3 + apps/desktop/src/locales/vi/messages.json | 3 + apps/desktop/src/locales/zh_CN/messages.json | 3 + apps/desktop/src/locales/zh_TW/messages.json | 3 + 55 files changed, 910 insertions(+), 745 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index a6296cb1376..347028a59fa 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -539,7 +539,7 @@ "message": "Hoofwagwoord moet weer intevoer word." }, "masterPasswordMinlength": { - "message": "Master password must be at least 8 characters long." + "message": "Hoofwagwoord moet ten minste 8 karakters lank wees." }, "masterPassDoesntMatch": { "message": "Hoofwagwoordbevestiging stem nie ooreen nie." @@ -1394,19 +1394,19 @@ "message": "Vergrendel by herbegin met hoofwagwoord" }, "deleteAccount": { - "message": "Delete account" + "message": "Skrap rekening" }, "deleteAccountDesc": { - "message": "Proceed below to delete your account and all vault data." + "message": "Gaan hieronder voort om u rekening en alle kluisdata te skrap." }, "deleteAccountWarning": { - "message": "Deleting your account is permanent. It cannot be undone." + "message": "Skrap van u rekening is permanent. Dit kan nie ontdaan word nie." }, "accountDeleted": { - "message": "Account deleted" + "message": "Rekening geskrap" }, "accountDeletedDesc": { - "message": "Your account has been closed and all associated data has been deleted." + "message": "U rekening is gesluit en alle geassosieerde data is geskrap." }, "preferences": { "message": "Voorkeure" @@ -1979,8 +1979,11 @@ "apiKey": { "message": "API-sleutel" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { - "message": "Organization is disabled." + "message": "Organisasie is gedeaktiveer." }, "disabledOrganizationFilterError": { "message": "Items in disabled Organizations cannot be accessed. Contact your Organization owner for assistance." diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 02f376b787b..fcc68d0b2dd 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "مفتاح API" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "تم تعطيل المؤسسة." }, diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 79ba742f852..dfcf7a2583a 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API açar" }, + "premiumSubcriptionRequired": { + "message": "Premium abunəlik tələb olunur" + }, "organizationIsDisabled": { "message": "Təşkilat sıradan çıxarıldı." }, diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index b9161505f51..28a9676d03e 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -15,7 +15,7 @@ "message": "Тыпы" }, "typeLogin": { - "message": "Уліковыя даныя" + "message": "Уваход" }, "typeCard": { "message": "Картка" @@ -45,10 +45,10 @@ "message": "Абагуліць" }, "moveToOrganization": { - "message": "Move to Organization" + "message": "Перамясціць у арганізацыю" }, "movedItemToOrg": { - "message": "$ITEMNAME$ moved to $ORGNAME$", + "message": "$ITEMNAME$ перамешчаны ў $ORGNAME$", "placeholders": { "itemname": { "content": "$1", @@ -61,7 +61,7 @@ } }, "moveToOrgDesc": { - "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." + "message": "Выберыце арганізацыю, у якую вы хочаце перамясціць гэты элемент. Пры перамяшчэнні ў арганізацыю ўсе правы ўласнасці на дадзены элемент пяройдуць да гэтай арганізацыі. Вы больш не будзеце адзіным уласнікам гэтага элемента пасля яго перамяшчэння." }, "attachments": { "message": "Далучэнні" @@ -101,7 +101,7 @@ "message": "Рэдагаванне элемента" }, "emailAddress": { - "message": "Адрас эл. пошты" + "message": "Адрас электроннай пошты" }, "verificationCodeTotp": { "message": "Код праверкі (TOTP)" @@ -199,7 +199,7 @@ "message": "Красавік" }, "may": { - "message": "Май" + "message": "Травень" }, "june": { "message": "Чэрвень" @@ -239,7 +239,7 @@ "message": "Пані" }, "dr": { - "message": "Док." + "message": "Доктар" }, "expirationMonth": { "message": "Месяц заканчэння" @@ -269,16 +269,16 @@ "message": "Прозвішча" }, "fullName": { - "message": "Full Name" + "message": "Поўнае імя" }, "address1": { - "message": "Радок адрасу 1" + "message": "Адрас 1" }, "address2": { - "message": "Радок адрасу 2" + "message": "Адрас 2" }, "address3": { - "message": "Радок адрасу 3" + "message": "Адрас 3" }, "cityTown": { "message": "Горад / Пасёлак" @@ -308,7 +308,7 @@ "message": "Рэдагаваць" }, "authenticatorKeyTotp": { - "message": "Ключ праверкі сапраўднасці (TOTP)" + "message": "Ключ аўтэнтыфікацыі (TOTP)" }, "folder": { "message": "Папка" @@ -332,18 +332,18 @@ "message": "Лагічнае" }, "cfTypeLinked": { - "message": "Linked", + "message": "Звязана", "description": "This describes a field that is 'linked' (related) to another field." }, "linkedValue": { - "message": "Linked value", + "message": "Звязанае значэнне", "description": "This describes a value that is 'linked' (related) to another value." }, "remove": { "message": "Выдаліць" }, "nameRequired": { - "message": "Патрэбна назва." + "message": "Патрабуецца назва." }, "addedItem": { "message": "Элемент дададзены" @@ -364,16 +364,16 @@ "message": "Вы ўпэўнены, што хочаце выдаліць гэты элемент?" }, "deletedItem": { - "message": "Выдалены элемент" + "message": "Элемент адпраўлены ў сметніцу" }, "overwritePasswordConfirmation": { "message": "Вы ўпэўнены, што хочаце перазапісаць бягучы пароль?" }, "overwriteUsername": { - "message": "Overwrite Username" + "message": "Перазапісаць імя карыстальніка" }, "overwriteUsernameConfirmation": { - "message": "Are you sure you want to overwrite the current username?" + "message": "Вы ўпэўнены, што хочаце перазапісаць бягучае імя карыстальніка?" }, "noneFolder": { "message": "Без папкі", @@ -386,7 +386,7 @@ "message": "Рэдагаваць папку" }, "regeneratePassword": { - "message": "Стварыць новы пароль" + "message": "Згенерыраваць новы пароль" }, "copyPassword": { "message": "Капіяваць пароль" @@ -395,22 +395,22 @@ "message": "Капіяваць URI" }, "copyVerificationCodeTotp": { - "message": "Copy Verification Code (TOTP)" + "message": "Капіяваць код праверкі (TOTP)" }, "length": { "message": "Даўжыня" }, "uppercase": { - "message": "Uppercase (A-Z)" + "message": "Вялікія літары (A-Z)" }, "lowercase": { - "message": "Lowercase (a-z)" + "message": "Маленькія літары (a-z)" }, "numbers": { - "message": "Numbers (0-9)" + "message": "Лічбы (0-9)" }, "specialCharacters": { - "message": "Special Characters (!@#$%^&*)" + "message": "Спецыяльныя сімвалы (!@#$%^&*)" }, "numWords": { "message": "Колькасць слоў" @@ -429,10 +429,10 @@ "message": "Закрыць" }, "minNumbers": { - "message": "Мін. колькасць лічбаў" + "message": "Мінімум лічбаў" }, "minSpecial": { - "message": "Мін. колькасць сімвалаў", + "message": "Мінімум сімвалаў", "description": "Minimum Special Characters" }, "ambiguous": { @@ -533,13 +533,13 @@ "message": "Памылковы адрас электроннай пошты." }, "masterPasswordRequired": { - "message": "Master password is required." + "message": "Патрабуецца асноўны пароль." }, "confirmMasterPasswordRequired": { - "message": "Master password retype is required." + "message": "Неабходна паўторна ўвесці асноўны пароль." }, "masterPasswordMinlength": { - "message": "Master password must be at least 8 characters long." + "message": "Асноўны пароль павінен быць даўжынёй не менш за 8 сімвалаў." }, "masterPassDoesntMatch": { "message": "Асноўныя паролі не супадаюць." @@ -560,25 +560,25 @@ "message": "Няма элементаў для паказу." }, "sendVerificationCode": { - "message": "Send a verification code to your email" + "message": "Адправіць код праверкі на вашу электронную пошту" }, "sendCode": { - "message": "Send Code" + "message": "Адправіць код" }, "codeSent": { - "message": "Code Sent" + "message": "Код адпраўлены" }, "verificationCode": { "message": "Код праверкі" }, "confirmIdentity": { - "message": "Confirm your identity to continue." + "message": "Пацвердзіце сваю асобу, каб працягнуць." }, "verificationCodeRequired": { "message": "Патрабуецца код праверкі." }, "invalidVerificationCode": { - "message": "Invalid verification code" + "message": "Памылковы код праверкі" }, "continue": { "message": "Працягнуць" @@ -650,7 +650,7 @@ "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "Use any WebAuthn enabled security key to access your account." + "message": "Выкарыстоўвайце любы ключ бяспекі WebAuthn для доступу да вашага ўліковага запісу." }, "emailTitle": { "message": "Электронная пошта" @@ -752,16 +752,16 @@ "message": "Загрузка..." }, "lockVault": { - "message": "Lock Vault" + "message": "Заблакіраваць сховішча" }, "passwordGenerator": { "message": "Генератар пароляў" }, "contactUs": { - "message": "Contact Us" + "message": "Звязацца з намі" }, "getHelp": { - "message": "Get Help" + "message": "Атрымаць даведку" }, "fileBugReport": { "message": "Паведаміць аб памылцы" @@ -898,7 +898,7 @@ "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "enableFavicon": { - "message": "Show website icons" + "message": "Паказваць значкі вэб-сайтаў" }, "faviconDesc": { "message": "Show a recognizable image next to each login." @@ -910,10 +910,10 @@ "message": "Пры згортванні акна, будзе паказвацца значок у вообласці апавяшчэнняў." }, "enableMinToMenuBar": { - "message": "Minimize to menu bar" + "message": "Згарнуць у панэль меню" }, "enableMinToMenuBarDesc": { - "message": "When minimizing the window, show an icon in the menu bar instead." + "message": "Пры згортванні акна, паказваць значок у панэлі меню." }, "enableCloseToTray": { "message": "Закрываць у вобласць апавяшчэнняў" @@ -922,10 +922,10 @@ "message": "Пры закрыцці акна, будзе паказвацца значок у вообласці апавяшчэнняў." }, "enableCloseToMenuBar": { - "message": "Close to menu bar" + "message": "Закрыць у панэлі меню" }, "enableCloseToMenuBarDesc": { - "message": "When closing the window, show an icon in the menu bar instead." + "message": "Пры закрыцці акна, паказваць значок у панэлі меню." }, "enableTray": { "message": "Уключыць значок у вобласці апавяшчэнняў" @@ -940,28 +940,28 @@ "message": "Пры першым запуску праграмы, будзе паказвацца значок у вобласці апавяшчэнняў." }, "startToMenuBar": { - "message": "Start to menu bar" + "message": "Запускаць у панэлі меню" }, "startToMenuBarDesc": { - "message": "When the application is first started, only show an icon in the menu bar." + "message": "Пры першым запуску праграмы паказваць толькі значок у панэлі меню." }, "openAtLogin": { - "message": "Start automatically on login" + "message": "Запускаць аўтаматычна пры ўваходзе" }, "openAtLoginDesc": { - "message": "Start the Bitwarden desktop application automatically on login." + "message": "Запускаць праграму Bitwarden для камп'ютара аўтаматычна пры ўваходзе ў сістэму." }, "alwaysShowDock": { - "message": "Always show in the Dock" + "message": "Заўсёды паказваць на панэлі Dock" }, "alwaysShowDockDesc": { - "message": "Show the Bitwarden icon in the Dock even when minimized to the menu bar." + "message": "Паказваць значок Bitwarden на панэлі Dock нават пры згортванні ў панэль меню." }, "confirmTrayTitle": { - "message": "Confirm disable tray" + "message": "Пацвердзіць адключэнне ў вобласці апавяшчэнняў" }, "confirmTrayDesc": { - "message": "Disabling this setting will also disable all other tray related settings." + "message": "Адключэнне гэтай налады таксама адключыць усе іншыя налады, якія звязаны з вобласцю апавяшчэнняў." }, "language": { "message": "Мова" @@ -1213,7 +1213,7 @@ "description": "Domain name. Ex. website.com" }, "domainName": { - "message": "Domain Name", + "message": "Імя дамена", "description": "Domain name. Ex. website.com" }, "host": { @@ -1277,32 +1277,32 @@ "message": "Фармат файла" }, "hCaptchaUrl": { - "message": "hCaptcha Url", + "message": "URL-адрас hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" }, "loadAccessibilityCookie": { - "message": "Load Accessibility Cookie" + "message": "Загрузіць cookie спецыяльных магчымасцяў" }, "registerAccessibilityUser": { - "message": "Register as an accessibility user at", + "message": "Зарэгістравацца ў якасці карыстальніка са спецыяльнымі магчымасцямі на", "description": "ex. Register as an accessibility user at hcaptcha.com" }, "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" + "message": "Скапіюйце і ўстаўце адпраўленую спасылку на вашу электронную пошту" }, "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", + "message": "Увядзіце URL-адрас для загрузкі cookie спецыяльных магчымасцяў для hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" }, "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", + "message": "Патрабуецца URL-адрас hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" }, "invalidUrl": { - "message": "Invalid Url" + "message": "Памылковы URL-адрас" }, "done": { - "message": "Done" + "message": "Гатова" }, "accessibilityCookieSaved": { "message": "Accessibility cookie saved!" @@ -1412,7 +1412,7 @@ "message": "Налады" }, "enableMenuBar": { - "message": "Уключыць значок у радку меню" + "message": "Уключыць значок у панэлі меню" }, "enableMenuBarDesc": { "message": "Заўсёды паказваць значок у радку меню." @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 43318ba30d9..00e18d0fcad 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "Ключ за API" }, + "premiumSubcriptionRequired": { + "message": "Изисква се платен абонамент" + }, "organizationIsDisabled": { "message": "Организацията е изключена." }, diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 3366cec0567..82114ee0a72 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 393ace9b398..a861d3a397a 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 0db1a8dc752..33c436a4bb3 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "Clau de l'API" }, + "premiumSubcriptionRequired": { + "message": "Cal una subscripció premium" + }, "organizationIsDisabled": { "message": "L'organització està inhabilitada." }, diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 8c76c54ba2c..f91cca5fc47 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index c332cc6aec9..3b388f1312f 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API-nøgle" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organisationen er deaktiveret." }, diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index a69cabe2e5b..d85be199e02 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API-Schlüssel" }, + "premiumSubcriptionRequired": { + "message": "Premium-Abonnement erforderlich" + }, "organizationIsDisabled": { "message": "Organisation ist deaktiviert." }, diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index ae7cf17790f..d2a55d40718 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "Kλειδί API" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index cc19647e03e..16815abde0b 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organisation is disabled." }, diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index f4783ec37b4..cf5a59dc563 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index a1389e79aaa..4bc416e01b9 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -24,10 +24,10 @@ "message": "Identity" }, "typeSecureNote": { - "message": "Secure Note" + "message": "Sekura noto" }, "folders": { - "message": "Folders" + "message": "Dosierujoj" }, "collections": { "message": "Collections" @@ -36,13 +36,13 @@ "message": "Search Vault" }, "addItem": { - "message": "Add Item" + "message": "Aldoni elementon" }, "shared": { - "message": "Shared" + "message": "Kundividita" }, "share": { - "message": "Share" + "message": "Kundividi" }, "moveToOrganization": { "message": "Move to Organization" @@ -64,13 +64,13 @@ "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." }, "attachments": { - "message": "Attachments" + "message": "Aldonaĵoj" }, "viewItem": { - "message": "View Item" + "message": "Vidi la elementon" }, "name": { - "message": "Name" + "message": "Nomo" }, "uri": { "message": "URI" @@ -86,22 +86,22 @@ } }, "newUri": { - "message": "New URI" + "message": "Nova URI" }, "username": { - "message": "Username" + "message": "Uzantnomo" }, "password": { - "message": "Password" + "message": "Pasvorto" }, "passphrase": { - "message": "Passphrase" + "message": "Pasfrazo" }, "editItem": { - "message": "Edit Item" + "message": "Redakti la elementon" }, "emailAddress": { - "message": "Email Address" + "message": "Retpoŝta adreso" }, "verificationCodeTotp": { "message": "Verification Code (TOTP)" @@ -110,10 +110,10 @@ "message": "Website" }, "notes": { - "message": "Notes" + "message": "Notoj" }, "customFields": { - "message": "Custom Fields" + "message": "Propraj kampoj" }, "launch": { "message": "Launch" @@ -139,28 +139,28 @@ "message": "Cardholder Name" }, "number": { - "message": "Number" + "message": "Numero" }, "brand": { "message": "Brand" }, "expiration": { - "message": "Expiration" + "message": "Validoperiodo" }, "securityCode": { - "message": "Security Code" + "message": "Kodo de sekureco" }, "identityName": { - "message": "Identity Name" + "message": "Nomo de la identilo" }, "company": { - "message": "Company" + "message": "Kompanio" }, "ssn": { - "message": "Social Security Number" + "message": "Numero de sociala asekuro" }, "passportNumber": { - "message": "Passport Number" + "message": "Numero de pasporto" }, "licenseNumber": { "message": "License Number" @@ -1394,7 +1394,7 @@ "message": "Lock with master password on restart" }, "deleteAccount": { - "message": "Delete account" + "message": "Forviŝi la konton" }, "deleteAccountDesc": { "message": "Proceed below to delete your account and all vault data." @@ -1403,10 +1403,10 @@ "message": "Deleting your account is permanent. It cannot be undone." }, "accountDeleted": { - "message": "Account deleted" + "message": "Konto forviŝita" }, "accountDeletedDesc": { - "message": "Your account has been closed and all associated data has been deleted." + "message": "Via konto estas fermita, kaj ĉiuj rilataj datumoj estas forviŝitaj." }, "preferences": { "message": "Preferences" @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index ad155d25463..bf6f57bd2de 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "Clave API" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "La organización está desactivada." }, diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 7fc3fe0e32c..06be1530ad1 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -533,10 +533,10 @@ "message": "Vigane e-posti aadress." }, "masterPasswordRequired": { - "message": "Master password is required." + "message": "Vajalik on ülemparooli sisestamine." }, "confirmMasterPasswordRequired": { - "message": "Master password retype is required." + "message": "Vajalik on ülemparooli uuesti sisestamine." }, "masterPasswordMinlength": { "message": "Ülemparool peab olema vähemalt 8 tähemärgi pikkune." @@ -898,10 +898,10 @@ "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "enableFavicon": { - "message": "Show website icons" + "message": "Kuva veebilehtede ikoone" }, "faviconDesc": { - "message": "Show a recognizable image next to each login." + "message": "Kuvab iga kirje kõrval lehekülje ikooni." }, "enableMinToTray": { "message": "Minimeeri tegumiribale" @@ -1385,28 +1385,28 @@ "message": "Kinnita Bitwardenisse sisselogimine." }, "autoPromptWindowsHello": { - "message": "Ask for Windows Hello on launch" + "message": "Küsi avamisel Windows Hello tuvastust" }, "autoPromptTouchId": { - "message": "Ask for Touch ID on launch" + "message": "Küsi avamisel Touch ID tuvastust" }, "lockWithMasterPassOnRestart": { "message": "Lukusta ülemparooliga, kui rakendus taaskäivitatakse" }, "deleteAccount": { - "message": "Delete account" + "message": "Kustuta konto" }, "deleteAccountDesc": { - "message": "Proceed below to delete your account and all vault data." + "message": "Jätkates kustutatakse sinu konto ja kõik hoidla andmed." }, "deleteAccountWarning": { - "message": "Deleting your account is permanent. It cannot be undone." + "message": "Konto kustutamine on ühekordne tegevus. Seda ei saa tagasi võtta." }, "accountDeleted": { - "message": "Account deleted" + "message": "Konto on kustutatud" }, "accountDeletedDesc": { - "message": "Your account has been closed and all associated data has been deleted." + "message": "Konto on suletud ja kõik sellega seonduvad andmed on kustutatud." }, "preferences": { "message": "Eelistused" @@ -1562,7 +1562,7 @@ "message": "Märkeruudu markeerimisel nõustud järgnevaga:" }, "acceptPoliciesRequired": { - "message": "Terms of Service and Privacy Policy have not been acknowledged." + "message": "Kasutustingimuste ja Privaatsuspoliitikaga pole nõustutud." }, "enableBrowserIntegration": { "message": "Lülita sisse brauseri integratsioon" @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API võti" }, + "premiumSubcriptionRequired": { + "message": "Vajalik on Premium versioon" + }, "organizationIsDisabled": { "message": "Organisatsiooni ligipääs on keelatud." }, @@ -1986,7 +1989,7 @@ "message": "Organisatsiooni alla kuuluvatele kirjetele ei ole ligipääsu. Kontakteeru oma organisatsiooni omanikuga." }, "neverLockWarning": { - "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." + "message": "Oled kindel, et soovid kasutada valikut \"Mitte kunagi\"? Sellega talletatakse sinu hoidla krüpteerimise võtit seadme mälus. Peaksid olema väga hoolas ja kindel, et seade on ohutu ja selles ei ole pahavara." }, "cardBrandMir": { "message": "Mir" diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 8fb9253d0e6..5b05618ec4f 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -9,46 +9,46 @@ "message": "Elementu guztiak" }, "favorites": { - "message": "Favorites" + "message": "Gogokoak" }, "types": { - "message": "Types" + "message": "Motak" }, "typeLogin": { - "message": "Login" + "message": "Saio-hasiera" }, "typeCard": { - "message": "Card" + "message": "Txartela" }, "typeIdentity": { - "message": "Identity" + "message": "Identitatea" }, "typeSecureNote": { - "message": "Secure Note" + "message": "Ohar segurua" }, "folders": { - "message": "Folders" + "message": "Karpetak" }, "collections": { - "message": "Collections" + "message": "Bildumak" }, "searchVault": { - "message": "Search Vault" + "message": "Kutxa Gotorrean bilatu" }, "addItem": { - "message": "Add Item" + "message": "Gehitu elementua" }, "shared": { - "message": "Shared" + "message": "Partekatua" }, "share": { - "message": "Share" + "message": "Partekatu" }, "moveToOrganization": { - "message": "Move to Organization" + "message": "Mugitu erakundera" }, "movedItemToOrg": { - "message": "$ITEMNAME$ moved to $ORGNAME$", + "message": "$ITEMNAME$ $ORGNAME$-ra mugituta", "placeholders": { "itemname": { "content": "$1", @@ -61,16 +61,16 @@ } }, "moveToOrgDesc": { - "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." + "message": "Aukeratu elementu hau zein erakundetara eraman nahi duzun. Erakunde batera pasatzeak elementuaren jabetza erakunde horretara transferitzen du. Zu ez zara elementu honen jabe zuzena izango mugitzen duzunean." }, "attachments": { - "message": "Attachments" + "message": "Eranskinak" }, "viewItem": { - "message": "View Item" + "message": "Bistaratu elementua" }, "name": { - "message": "Name" + "message": "Izena" }, "uri": { "message": "URI" @@ -86,508 +86,508 @@ } }, "newUri": { - "message": "New URI" + "message": "URI berria" }, "username": { - "message": "Username" + "message": "Erabiltzaile izena" }, "password": { - "message": "Password" + "message": "Pasahitza" }, "passphrase": { - "message": "Passphrase" + "message": "Pasaesaldia" }, "editItem": { - "message": "Edit Item" + "message": "Editatu elementua" }, "emailAddress": { - "message": "Email Address" + "message": "Email helbidea" }, "verificationCodeTotp": { - "message": "Verification Code (TOTP)" + "message": "Egiaztatze-kodea (TOTP)" }, "website": { - "message": "Website" + "message": "Webgunea" }, "notes": { - "message": "Notes" + "message": "Oharrak" }, "customFields": { - "message": "Custom Fields" + "message": "Eremu pertsonalizatuak" }, "launch": { - "message": "Launch" + "message": "Abiarazi" }, "copyValue": { - "message": "Copy Value", + "message": "Kopiatu balioa", "description": "Copy value to clipboard" }, "minimizeOnCopyToClipboard": { - "message": "Minimize when copying to clipboard" + "message": "Minimizatu arbelera kopiatzean" }, "minimizeOnCopyToClipboardDesc": { - "message": "Minimize application when copying an item's data to the clipboard." + "message": "Aplikazioa minimizatu elementu bateko datuak arbelean kopiatzean." }, "toggleVisibility": { - "message": "Toggle Visibility" + "message": "Txandaketa ikusgarritasuna" }, "toggleCollapse": { - "message": "Toggle Collapse", + "message": "Hondoa jo", "description": "Toggling an expand/collapse state." }, "cardholderName": { - "message": "Cardholder Name" + "message": "Txartelaren titularraren izena" }, "number": { - "message": "Number" + "message": "Zenbakia" }, "brand": { - "message": "Brand" + "message": "Marka" }, "expiration": { - "message": "Expiration" + "message": "Iraungitze data" }, "securityCode": { - "message": "Security Code" + "message": "Segurtasun-kodea" }, "identityName": { - "message": "Identity Name" + "message": "Identitate izena" }, "company": { - "message": "Company" + "message": "Enpresa" }, "ssn": { - "message": "Social Security Number" + "message": "Segurtasun sozialaren zenbakia" }, "passportNumber": { - "message": "Passport Number" + "message": "Pasaporte zenbakia" }, "licenseNumber": { - "message": "License Number" + "message": "Lizentzia zenbakia" }, "email": { - "message": "Email" + "message": "Emaila" }, "phone": { - "message": "Phone" + "message": "Telefonoa" }, "address": { - "message": "Address" + "message": "Helbidea" }, "premiumRequired": { - "message": "Premium Required" + "message": "Premium izatea beharrezkoa da" }, "premiumRequiredDesc": { - "message": "A premium membership is required to use this feature." + "message": "Premium bazkidetza beharrezkoa da ezaugarri hau erabiltzeko." }, "errorOccurred": { - "message": "An error has occurred." + "message": "Akats bat gertatu da." }, "error": { - "message": "Error" + "message": "Akatsa" }, "january": { - "message": "January" + "message": "Urtarrila" }, "february": { - "message": "February" + "message": "Otsaila" }, "march": { - "message": "March" + "message": "Martxoa" }, "april": { - "message": "April" + "message": "Apirila" }, "may": { - "message": "May" + "message": "Maiatza" }, "june": { - "message": "June" + "message": "Ekaina" }, "july": { - "message": "July" + "message": "Uztaila" }, "august": { - "message": "August" + "message": "Abuztua" }, "september": { - "message": "September" + "message": "Iraila" }, "october": { - "message": "October" + "message": "Urria" }, "november": { - "message": "November" + "message": "Azaroa" }, "december": { - "message": "December" + "message": "Abendua" }, "ex": { - "message": "ex.", + "message": "adb.", "description": "Short abbreviation for 'example'." }, "title": { - "message": "Title" + "message": "Titulua" }, "mr": { - "message": "Mr" + "message": "Jn." }, "mrs": { - "message": "Mrs" + "message": "And." }, "ms": { - "message": "Ms" + "message": "And." }, "dr": { - "message": "Dr" + "message": "Jn." }, "expirationMonth": { - "message": "Expiration Month" + "message": "Iraungitze hilabetea" }, "expirationYear": { - "message": "Expiration Year" + "message": "Iraungitze urtea" }, "select": { - "message": "Select" + "message": "Hautatu" }, "other": { - "message": "Other" + "message": "Bestelakoak" }, "generatePassword": { - "message": "Generate Password" + "message": "Sortu pasahitza" }, "type": { - "message": "Type" + "message": "Mota" }, "firstName": { - "message": "First Name" + "message": "Izena" }, "middleName": { - "message": "Middle Name" + "message": "Bigarren izena" }, "lastName": { - "message": "Last Name" + "message": "Abizena" }, "fullName": { - "message": "Full Name" + "message": "Izen osoa" }, "address1": { - "message": "Address 1" + "message": "1go helbidea" }, "address2": { - "message": "Address 2" + "message": "2. helbidea" }, "address3": { - "message": "Address 3" + "message": "3. helbidea" }, "cityTown": { - "message": "City / Town" + "message": "Hiria / Herria" }, "stateProvince": { - "message": "State / Province" + "message": "Estatua / Probintzia" }, "zipPostalCode": { - "message": "Zip / Postal Code" + "message": "Posta kodea" }, "country": { - "message": "Country" + "message": "Herrialdea" }, "save": { - "message": "Save" + "message": "Gorde" }, "cancel": { - "message": "Cancel" + "message": "Ezeztatu" }, "delete": { - "message": "Delete" + "message": "Ezabatu" }, "favorite": { - "message": "Favorite" + "message": "Gogokoa" }, "edit": { - "message": "Edit" + "message": "Editatu" }, "authenticatorKeyTotp": { - "message": "Authenticator Key (TOTP)" + "message": "Autentifikazio-gakoa (TOTP)" }, "folder": { - "message": "Folder" + "message": "Karpeta" }, "newCustomField": { - "message": "New Custom Field" + "message": "Eremu pertsonalizatu berria" }, "value": { - "message": "Value" + "message": "Balioa" }, "dragToSort": { - "message": "Drag to sort" + "message": "Arrastatu txukuntzeko" }, "cfTypeText": { - "message": "Text" + "message": "Testua" }, "cfTypeHidden": { - "message": "Hidden" + "message": "Ezkutatua" }, "cfTypeBoolean": { - "message": "Boolean" + "message": "Boolearra" }, "cfTypeLinked": { - "message": "Linked", + "message": "Konektatuta", "description": "This describes a field that is 'linked' (related) to another field." }, "linkedValue": { - "message": "Linked value", + "message": "Balioa lotuta", "description": "This describes a value that is 'linked' (related) to another value." }, "remove": { - "message": "Remove" + "message": "Ezabatu" }, "nameRequired": { - "message": "Name is required." + "message": "Izena beharrezkoa da." }, "addedItem": { - "message": "Added item" + "message": "Elementua gehituta" }, "editedItem": { - "message": "Edited item" + "message": "Elementua editatuta" }, "deleteItem": { - "message": "Delete Item" + "message": "Ezabatu elementua" }, "deleteFolder": { - "message": "Delete Folder" + "message": "Ezabatu karpeta" }, "deleteAttachment": { - "message": "Delete Attachment" + "message": "Ezabatu eranskina" }, "deleteItemConfirmation": { - "message": "Do you really want to send to the trash?" + "message": "Ziur zaude elementu hau zakarrontzira bidali nahi duzula?" }, "deletedItem": { - "message": "Sent item to trash" + "message": "Elementua zakarrontzira bidalia" }, "overwritePasswordConfirmation": { - "message": "Are you sure you want to overwrite the current password?" + "message": "Ziur al zaude pasahitza berridatzi nahi duzula?" }, "overwriteUsername": { - "message": "Overwrite Username" + "message": "Erabiltzaile-izena berridatzi" }, "overwriteUsernameConfirmation": { - "message": "Are you sure you want to overwrite the current username?" + "message": "Ziur al zaude erabiltzaile-izena berridatzi nahi duzula?" }, "noneFolder": { - "message": "No Folder", + "message": "Karpetarik ez", "description": "This is the folder for uncategorized items" }, "addFolder": { - "message": "Add Folder" + "message": "Gehitu karpeta" }, "editFolder": { - "message": "Edit Folder" + "message": "Editatu Karpeta" }, "regeneratePassword": { - "message": "Regenerate Password" + "message": "Berrezarri pasahitza" }, "copyPassword": { - "message": "Copy Password" + "message": "Kopiatu pasahitza" }, "copyUri": { - "message": "Copy URI" + "message": "Kopiatu URIa" }, "copyVerificationCodeTotp": { - "message": "Copy Verification Code (TOTP)" + "message": "Kopiatu egiaztatze-kodea (TOTP)" }, "length": { - "message": "Length" + "message": "Luzera" }, "uppercase": { - "message": "Uppercase (A-Z)" + "message": "Letra larria (A-Z)" }, "lowercase": { - "message": "Lowercase (a-z)" + "message": "Letra txikia (a-z)" }, "numbers": { - "message": "Numbers (0-9)" + "message": "Zenbakiak (0-9)" }, "specialCharacters": { - "message": "Special Characters (!@#$%^&*)" + "message": "Karaktere bereziak (!@#$%^&*)" }, "numWords": { - "message": "Number of Words" + "message": "Hitz kopurua" }, "wordSeparator": { - "message": "Word Separator" + "message": "Hitz banatzailea" }, "capitalize": { - "message": "Capitalize", + "message": "Hasierako letra larria", "description": "Make the first letter of a work uppercase." }, "includeNumber": { - "message": "Include Number" + "message": "Sartu zenbakia" }, "close": { - "message": "Close" + "message": "Itxi" }, "minNumbers": { - "message": "Minimum Numbers" + "message": "Gutxieneko zenbaki kopurua" }, "minSpecial": { - "message": "Minimum Special", + "message": "Gutxieneko karaktere bereziak", "description": "Minimum Special Characters" }, "ambiguous": { - "message": "Avoid Ambiguous Characters" + "message": "Saihestu karaktere anbiguoak" }, "searchCollection": { - "message": "Search Collection" + "message": "Bilatu bilduma" }, "searchFolder": { - "message": "Search Folder" + "message": "Bilatu karpeta" }, "searchFavorites": { - "message": "Search Favorites" + "message": "Bilatu gogokoak" }, "searchType": { - "message": "Search Type", + "message": "Bilaketa mota", "description": "Search item type" }, "newAttachment": { - "message": "Add New Attachment" + "message": "Gehitu eranskin berria" }, "deletedAttachment": { - "message": "Deleted attachment" + "message": "Eranskina ezabatuta" }, "deleteAttachmentConfirmation": { - "message": "Are you sure you want to delete this attachment?" + "message": "Ziur zaude eranskina ezabatu nahi duzula?" }, "attachmentSaved": { - "message": "The attachment has been saved." + "message": "Eranskina gorde da." }, "file": { - "message": "File" + "message": "Fitxategia" }, "selectFile": { - "message": "Select a file." + "message": "Hautatu fitxategia." }, "maxFileSize": { - "message": "Maximum file size is 500 MB." + "message": "Eranskinaren gehienezko tamaina 500MB." }, "updateKey": { - "message": "You cannot use this feature until you update your encryption key." + "message": "Ezin duzu ezaugarri hau erabili zifratze-gakoa eguneratu arte." }, "editedFolder": { - "message": "Edited folder" + "message": "Karpeta editatuta" }, "addedFolder": { - "message": "Added folder" + "message": "Karpeta gehituta" }, "deleteFolderConfirmation": { - "message": "Are you sure you want to delete this folder?" + "message": "Ziur al zaude karpeta hau ezabatu nahi duzula?" }, "deletedFolder": { - "message": "Deleted folder" + "message": "Karpeta ezabatuta" }, "loginOrCreateNewAccount": { - "message": "Log in or create a new account to access your secure vault." + "message": "Saioa hasi edo sortu kontu berri bat zure kutxa gotorrera sartzeko." }, "createAccount": { - "message": "Create Account" + "message": "Sortu kontua" }, "logIn": { - "message": "Log In" + "message": "Hasi saioa" }, "submit": { - "message": "Submit" + "message": "Bidali" }, "masterPass": { - "message": "Master Password" + "message": "Pasahitz nagusia" }, "masterPassDesc": { - "message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it." + "message": "Pasahitz nagusia, kutxa gotorrera sartzeko erabiltzen duzun pasahitza da. Oso garrantzitsua da ez ahaztea, ahazten baduzu, ez dago pasahitza berreskuratzeko modurik." }, "masterPassHintDesc": { - "message": "A master password hint can help you remember your password if you forget it." + "message": "Pasahitz nagusia ahazten baduzu, pista batek pasahitza gogoratzen lagunduko dizu." }, "reTypeMasterPass": { - "message": "Re-type Master Password" + "message": "Idatzi berriro pasahitz nagusia" }, "masterPassHint": { - "message": "Master Password Hint (optional)" + "message": "Pasahitz nagusirako pista (aukerakoa)" }, "settings": { - "message": "Settings" + "message": "Ezarpenak" }, "passwordHint": { - "message": "Password Hint" + "message": "Pasahitza gogoratzeko pista" }, "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." + "message": "Sartu zure kontuko emaila pasahitz nagusiaren pista jasotzeko." }, "getMasterPasswordHint": { - "message": "Get master password hint" + "message": "Jaso pasahitz nagusiaren pista" }, "emailRequired": { - "message": "Email address is required." + "message": "Emaila derrigorrezkoa da." }, "invalidEmail": { - "message": "Invalid email address." + "message": "Email helbide baliogabea." }, "masterPasswordRequired": { - "message": "Master password is required." + "message": "Pasahitz nagusia derrigorrezkoa da." }, "confirmMasterPasswordRequired": { - "message": "Master password retype is required." + "message": "Pasahitz nagusia berridaztea derrigorrezkoa da." }, "masterPasswordMinlength": { - "message": "Master password must be at least 8 characters long." + "message": "Pasahitz nagusiak gutxienez 8 karaktere izan behar ditu." }, "masterPassDoesntMatch": { - "message": "Master password confirmation does not match." + "message": "Pasahitz nagusiaren egiaztatzea ez dator bat." }, "newAccountCreated": { - "message": "Your new account has been created! You may now log in." + "message": "Zure kontua egina dago. Orain saioa has dezakezu." }, "masterPassSent": { - "message": "We've sent you an email with your master password hint." + "message": "Mezu elektroniko bat bidali dizugu zure pasahitz nagusiaren pistarekin." }, "unexpectedError": { - "message": "An unexpected error has occurred." + "message": "Ustekabeko akatsa gertatu da." }, "itemInformation": { - "message": "Item Information" + "message": "Elementuaren informazioa" }, "noItemsInList": { - "message": "There are no items to list." + "message": "Ez dago erakusteko elementurik." }, "sendVerificationCode": { - "message": "Send a verification code to your email" + "message": "Bidali egiaztatze-kodea zure emailera" }, "sendCode": { - "message": "Send Code" + "message": "Bidali kodea" }, "codeSent": { - "message": "Code Sent" + "message": "Kodea bidalia" }, "verificationCode": { - "message": "Verification Code" + "message": "Egiaztatze-kodea" }, "confirmIdentity": { - "message": "Confirm your identity to continue." + "message": "Jarraitzeko, berretsi zure identitatea." }, "verificationCodeRequired": { - "message": "Verification code is required." + "message": "Egiaztatze-kodea behar da." }, "invalidVerificationCode": { - "message": "Invalid verification code" + "message": "Egiaztatze-kodea ez da baliozkoa" }, "continue": { - "message": "Continue" + "message": "Jarraitu" }, "enterVerificationCodeApp": { - "message": "Enter the 6 digit verification code from your authenticator app." + "message": "Sartu zure autentifikazio aplikazioaren 6 digituko egiaztatze kodea." }, "enterVerificationCodeEmail": { - "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", + "message": "Sartu $EMAIL$-era bidalitako 6 digituko egiaztatze-kodea.", "placeholders": { "email": { "content": "$1", @@ -596,7 +596,7 @@ } }, "verificationCodeEmailSent": { - "message": "Verification email sent to $EMAIL$.", + "message": "Egiaztatze emaila $EMAIL$-era bidalia.", "placeholders": { "email": { "content": "$1", @@ -605,213 +605,213 @@ } }, "rememberMe": { - "message": "Remember me" + "message": "Gogora nazazu" }, "sendVerificationCodeEmailAgain": { - "message": "Send verification code email again" + "message": "Berbidali email bidezko egiaztatze-kodea" }, "useAnotherTwoStepMethod": { - "message": "Use another two-step login method" + "message": "Erabili bi urratseko saio hasierarako beste modu bat" }, "insertYubiKey": { - "message": "Insert your YubiKey into your computer's USB port, then touch its button." + "message": "Sartu zure YubiKey-a ordenagailuko USB atakan, ondoren, sakatu bere botoia." }, "insertU2f": { - "message": "Insert your security key into your computer's USB port. If it has a button, touch it." + "message": "Sartu zure segurtasun-gakoa ordenagailuaren USB atakan. Botoia badu, sakatu ezazu." }, "recoveryCodeDesc": { - "message": "Lost access to all of your two-factor providers? Use your recovery code to disable all two-factor providers from your account." + "message": "Bi urratseko egiaztatzeko modu guztietarako sarbidea galdu duzu? Erabili zure berreskuratze-kodea zure kontuko bi urratseko egiaztatze hornitzaile guztiak desaktibatzeko." }, "recoveryCodeTitle": { - "message": "Recovery Code" + "message": "Berreskuratze-kodea" }, "authenticatorAppTitle": { - "message": "Authenticator App" + "message": "Autentifikazio aplikazioa" }, "authenticatorAppDesc": { - "message": "Use an authenticator app (such as Authy or Google Authenticator) to generate time-based verification codes.", + "message": "Erabili autentifikazio aplikazio bat (adibidez, Authy edo Google Authenticator) denboran oinarritutako egiaztatze-kodeak sortzeko.", "description": "'Authy' and 'Google Authenticator' are product names and should not be translated." }, "yubiKeyTitle": { - "message": "YubiKey OTP Security Key" + "message": "YubiKey OTP segurtasun-gakoa" }, "yubiKeyDesc": { - "message": "Use a YubiKey to access your account. Works with YubiKey 4, 4 Nano, 4C, and NEO devices." + "message": "Erabili YubiKey zure kontuan sartzeko. YubiKey 4, 4 Nano, 4C eta NEO gailuekin dabil." }, "duoDesc": { - "message": "Verify with Duo Security using the Duo Mobile app, SMS, phone call, or U2F security key.", + "message": "Egiaztatu Duo Securityrekin Duo Mobile aplikazioa, SMS, telefono deia edo U2F segurtasun-gakoa erabiliz.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { - "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", + "message": "Egiaztatu zure erakunderako Duo Securityrekin Duo Mobile aplikazioa, SMS, telefono deia edo U2F segurtasun-gakoa erabiliz.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "Use any WebAuthn enabled security key to access your account." + "message": "Erabili gaitutako edozein WebAuthn segurtasun-gako zure kontura sartzeko." }, "emailTitle": { - "message": "Email" + "message": "Emaila" }, "emailDesc": { - "message": "Verification codes will be emailed to you." + "message": "Egiaztatze-kodeak email bidez bidaliko dira." }, "loginUnavailable": { - "message": "Login Unavailable" + "message": "Ez dago eskuragarri saio-hasierarik" }, "noTwoStepProviders": { - "message": "This account has two-step login enabled, however, none of the configured two-step providers are supported by this device." + "message": "Kontu honek bi urratseko saio hasiera du gaituta, baina ezarritako bi urratserako hornitzailea ez da gailu honekin bateragarria." }, "noTwoStepProviders2": { - "message": "Please add additional providers that are better supported across devices (such as an authenticator app)." + "message": "Mesedez, gehitu gailu guztietan bateragarriagoa den beste hornitzaile bat (adibidez, autentifikazio-aplikazioa)." }, "twoStepOptions": { - "message": "Two-step Login Options" + "message": "Bi urratseko saio hasieraren aukerak" }, "selfHostedEnvironment": { - "message": "Self-hosted Environment" + "message": "Ostatze ingurune propioa" }, "selfHostedEnvironmentFooter": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation." + "message": "Bitwarden instalatzeko, zehaztu ostatatze propioaren oinarrizko URL-a." }, "customEnvironment": { - "message": "Custom Environment" + "message": "Ingurune pertsonalizatua" }, "customEnvironmentFooter": { - "message": "For advanced users. You can specify the base URL of each service independently." + "message": "Erabiltzaile aurreratuentzat. Zerbitzu bakoitzarentzako oinarrizko URL-a zehaztu dezakezu independienteki." }, "baseUrl": { - "message": "Server URL" + "message": "Zerbitzariaren URL-a" }, "apiUrl": { - "message": "API Server URL" + "message": "API zerbitzariaren URL-a" }, "webVaultUrl": { - "message": "Web Vault Server URL" + "message": "Web kutxa gotorreko zerbitzariaren URL-a" }, "identityUrl": { - "message": "Identity Server URL" + "message": "Identitate zerbitzariaren URL-a" }, "notificationsUrl": { - "message": "Notifications Server URL" + "message": "Jakinarazpenen zerbitzariaren URL-a" }, "iconsUrl": { - "message": "Icons Server URL" + "message": "Ikonoen zerbitzariaren URL-a" }, "environmentSaved": { - "message": "The environment URLs have been saved." + "message": "Inguruneko URL-ak gorde dira." }, "ok": { - "message": "Ok" + "message": "Ados" }, "yes": { - "message": "Yes" + "message": "Bai" }, "no": { - "message": "No" + "message": "Ez" }, "overwritePassword": { - "message": "Overwrite Password" + "message": "Berridatzi pasahitza" }, "learnMore": { - "message": "Learn more" + "message": "Gehiago ikasi" }, "featureUnavailable": { - "message": "Feature Unavailable" + "message": "Ezaugarria ez dago erabilgarri" }, "loggedOut": { - "message": "Logged out" + "message": "Saioa itxita" }, "loginExpired": { - "message": "Your login session has expired." + "message": "Saioa amaitu da." }, "logOutConfirmation": { - "message": "Are you sure you want to log out?" + "message": "Ziur zaude saioa itxi nahi duzula?" }, "logOut": { - "message": "Log out" + "message": "Itxi saioa" }, "addNewLogin": { - "message": "Add New Login" + "message": "Saio-hasiera berria gehitu" }, "addNewItem": { - "message": "Add New Item" + "message": "Gehitu elementu berria" }, "addNewFolder": { - "message": "Add New Folder" + "message": "Karpeta berria gehitu" }, "view": { - "message": "View" + "message": "Erakutsi" }, "account": { - "message": "Account" + "message": "Kontua" }, "loading": { - "message": "Loading..." + "message": "Kargatzen..." }, "lockVault": { - "message": "Lock Vault" + "message": "Blokeatu kutxa gotorra" }, "passwordGenerator": { - "message": "Password Generator" + "message": "Pasahitz sortzailea" }, "contactUs": { - "message": "Contact Us" + "message": "Jarri gurekin harremanetan" }, "getHelp": { - "message": "Get Help" + "message": "Jaso laguntza" }, "fileBugReport": { - "message": "File a Bug Report" + "message": "Akats-txostena bidali" }, "blog": { - "message": "Blog" + "message": "Bloga" }, "followUs": { - "message": "Follow Us" + "message": "Jarraitu gaitzazu" }, "syncVault": { - "message": "Sync Vault" + "message": "Sinkronizatu kutxa gotorra" }, "changeMasterPass": { - "message": "Change Master Password" + "message": "Aldatu pasahitz nagusia" }, "changeMasterPasswordConfirmation": { - "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + "message": "Zure pasahitz nagusia alda dezakezu bitwarden.com webgunean. Orain joan nahi duzu webgunera?" }, "fingerprintPhrase": { - "message": "Fingerprint Phrase", + "message": "Hatz-marka digitalaren esaldia", "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": { - "message": "Your account's fingerprint phrase", + "message": "Zure kontuaren hatz-marka esaldia", "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": "Go to Web Vault" + "message": "Joan webguneko kutxa gotorrera" }, "getMobileApp": { - "message": "Get Mobile App" + "message": "Lortu aplikazio mugikorra" }, "getBrowserExtension": { - "message": "Get Browser Extension" + "message": "Lortu nabigatzaile gehigarriak" }, "syncingComplete": { - "message": "Syncing complete" + "message": "Sinkronizatu da" }, "syncingFailed": { - "message": "Syncing failed" + "message": "Sinkronizazioak huts egin du" }, "yourVaultIsLocked": { - "message": "Your vault is locked. Verify your identity to continue." + "message": "Zure kutxa gotorra blokeatuta dago. Egiaztatu zure identitatea jarraitzeko." }, "unlock": { - "message": "Unlock" + "message": "Desblokeatu" }, "loggedInAsOn": { - "message": "Logged in as $EMAIL$ on $HOSTNAME$.", + "message": "$HOSTNAME$-en $EMAIL$ bezala saioa hasita.", "placeholders": { "email": { "content": "$1", @@ -824,174 +824,174 @@ } }, "invalidMasterPassword": { - "message": "Invalid master password" + "message": "Pasahitz nagusi baliogabea" }, "twoStepLoginConfirmation": { - "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be enabled on the bitwarden.com web vault. Do you want to visit the website now?" + "message": "Bi urratseko saio hasiera dela eta, zure kontua seguruagoa da, beste aplikazio/gailu batekin saioa hastea eskatzen baitizu; adibidez, segurtasun-gako, autentifikazio-aplikazio, SMS, telefono dei edo email bidez. Bi urratseko saio hasiera bitwarden.com webgunean aktibatu daiteke. Orain joan nahi duzu webgunera?" }, "twoStepLogin": { - "message": "Two-step Login" + "message": "Bi urratseko saio hasiera" }, "vaultTimeout": { - "message": "Vault timeout" + "message": "Kutxa gotorraren itxaronaldia" }, "vaultTimeoutDesc": { - "message": "Choose when your vault will take the vault timeout action." + "message": "Aukeratu kutxa gotorraren itxaronaldia eta egingo den ekintza." }, "immediately": { - "message": "Immediately" + "message": "Berehala" }, "tenSeconds": { - "message": "10 seconds" + "message": "10 segundu" }, "twentySeconds": { - "message": "20 seconds" + "message": "20 segundu" }, "thirtySeconds": { - "message": "30 seconds" + "message": "30 segundu" }, "oneMinute": { - "message": "1 minute" + "message": "Minutu 1" }, "twoMinutes": { - "message": "2 minutes" + "message": "2 minutu" }, "fiveMinutes": { - "message": "5 minutes" + "message": "5 minutu" }, "fifteenMinutes": { - "message": "15 minutes" + "message": "15 minutu" }, "thirtyMinutes": { - "message": "30 minutes" + "message": "30 minutu" }, "oneHour": { - "message": "1 hour" + "message": "Ordu 1" }, "fourHours": { - "message": "4 hours" + "message": "4 ordu" }, "onIdle": { - "message": "On system idle" + "message": "Sistema gelditzean" }, "onSleep": { - "message": "On system sleep" + "message": "Sistema lokartzean" }, "onLocked": { - "message": "On system lock" + "message": "Sistema blokeatzean" }, "onRestart": { - "message": "On restart" + "message": "Berrabiarazten denean" }, "never": { - "message": "Never" + "message": "Inoiz ez" }, "security": { - "message": "Security" + "message": "Segurtasuna" }, "clearClipboard": { - "message": "Clear clipboard", + "message": "Hustu arbela", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "clearClipboardDesc": { - "message": "Automatically clear copied values from your clipboard.", + "message": "Ezabatu automatikoki arbelean kopiatutako balioak.", "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "enableFavicon": { - "message": "Show website icons" + "message": "Erakutsi webguneko ikonoak" }, "faviconDesc": { - "message": "Show a recognizable image next to each login." + "message": "Erakutsi irudi bat saio-hasiera bakoitzaren ondoan." }, "enableMinToTray": { - "message": "Minimize to tray icon" + "message": "Minimizatu erretiluko ikonora" }, "enableMinToTrayDesc": { - "message": "When minimizing the window, show an icon in the system tray instead." + "message": "Leihoa minimizatzean, bistaratu ikono bat sistemaren erretiluan." }, "enableMinToMenuBar": { - "message": "Minimize to menu bar" + "message": "Minimizatu menu-barrara" }, "enableMinToMenuBarDesc": { - "message": "When minimizing the window, show an icon in the menu bar instead." + "message": "Leihoa minimizatzean, ikono bat erakutsi menu-barran." }, "enableCloseToTray": { - "message": "Close to tray icon" + "message": "Itxi erretiluko ikonora" }, "enableCloseToTrayDesc": { - "message": "When closing the window, show an icon in the system tray instead." + "message": "Leihoa ixtean, bistaratu ikono bat sistemaren erretiluan." }, "enableCloseToMenuBar": { - "message": "Close to menu bar" + "message": "Itxi menu-barrara" }, "enableCloseToMenuBarDesc": { - "message": "When closing the window, show an icon in the menu bar instead." + "message": "Leihoa ixtean, ikono bat erakutsi menu-barran." }, "enableTray": { - "message": "Enable tray icon" + "message": "Gaitu erretiluko ikonoa" }, "enableTrayDesc": { - "message": "Always show an icon in the system tray." + "message": "Erakutsi ikonoa beti sistemaren erretiluan." }, "startToTray": { - "message": "Start to tray icon" + "message": "Hasi erretiluko ikonoan" }, "startToTrayDesc": { - "message": "When the application is first started, only show an icon in the system tray." + "message": "Aplikazioa abiaraztean, ikonoa sistemaren erretiluan baino ez da agertuko." }, "startToMenuBar": { - "message": "Start to menu bar" + "message": "Hasi menu-barran" }, "startToMenuBarDesc": { - "message": "When the application is first started, only show an icon in the menu bar." + "message": "Aplikazioa abiaraztean, ikonoa menu-barran baino ez da agertuko." }, "openAtLogin": { - "message": "Start automatically on login" + "message": "Hasi automatikoki saioa hastean" }, "openAtLoginDesc": { - "message": "Start the Bitwarden desktop application automatically on login." + "message": "Hasi mahaigaineko Bitwarden aplikazioa automatikoki saioa hastean." }, "alwaysShowDock": { - "message": "Always show in the Dock" + "message": "Erakutsi beti Dock-ean" }, "alwaysShowDockDesc": { - "message": "Show the Bitwarden icon in the Dock even when minimized to the menu bar." + "message": "Bistaratu Bitwarden ikonoa Dock-ean, baita menu-barran minimizatzen denean ere." }, "confirmTrayTitle": { - "message": "Confirm disable tray" + "message": "Baieztatu erretilua desgaitzea" }, "confirmTrayDesc": { - "message": "Disabling this setting will also disable all other tray related settings." + "message": "Hau desgaituz gero, erretiluarekin lotutako gainerako ezarpen guztiak desgaituko dira." }, "language": { - "message": "Language" + "message": "Hizkuntza" }, "languageDesc": { - "message": "Change the language used by the application. Restart is required." + "message": "Aldatu aplikazioan erabiltzen den hizkuntza. Berrabiarazi egin behar da." }, "theme": { - "message": "Theme" + "message": "Gaia" }, "themeDesc": { - "message": "Change the application's color theme." + "message": "Aldatu aplikaziorako kolore gaia." }, "dark": { - "message": "Dark", + "message": "Iluna", "description": "Dark color" }, "light": { - "message": "Light", + "message": "Argia", "description": "Light color" }, "copy": { - "message": "Copy", + "message": "Kopiatu", "description": "Copy to clipboard" }, "checkForUpdates": { - "message": "Check for Updates…" + "message": "Egiaztatu eguneraketarik dagoen…" }, "version": { - "message": "Version $VERSION_NUM$", + "message": "$VERSION_NUM$ bertsioa", "placeholders": { "version_num": { "content": "$1", @@ -1000,10 +1000,10 @@ } }, "restartToUpdate": { - "message": "Restart to Update" + "message": "Berrabiarazi eguneratzeko" }, "restartToUpdateDesc": { - "message": "Version $VERSION_NUM$ is ready to install. You must restart the application to complete the installation. Do you want to restart and update now?", + "message": "$VERSION_NUM$ bertsioa instalatzeko prest dago. Aplikazioa berrabiarazi behar duzu instalazioa osatzeko. Orain berrabiarazi eta eguneratu nahi duzu?", "placeholders": { "version_num": { "content": "$1", @@ -1012,87 +1012,87 @@ } }, "updateAvailable": { - "message": "Update Available" + "message": "Eguneraketa eskuragarri" }, "updateAvailableDesc": { - "message": "An update was found. Do you want to download it now?" + "message": "Eguneraketa bat aurkitu da. Orain deskargatu nahi duzu?" }, "restart": { - "message": "Restart" + "message": "Berrabiarazi" }, "later": { - "message": "Later" + "message": "Geroago" }, "noUpdatesAvailable": { - "message": "No updates are currently available. You are using the latest version." + "message": "Ez dago eguneratzerik erabilgarri. Azken bertsioa erabiltzen ari zara." }, "updateError": { - "message": "Update Error" + "message": "Eguneraketa akatsa" }, "unknown": { - "message": "Unknown" + "message": "Ezezaguna" }, "copyUsername": { - "message": "Copy Username" + "message": "Kopiatu erabiltzaile-izena" }, "copyNumber": { - "message": "Copy Number", + "message": "Kopiatu zenbakia", "description": "Copy credit card number" }, "copySecurityCode": { - "message": "Copy Security Code", + "message": "Kopiatu segurtasun-kodea", "description": "Copy credit card security code (CVV)" }, "premiumMembership": { - "message": "Premium Membership" + "message": "Premium bazkidea" }, "premiumManage": { - "message": "Manage Membership" + "message": "Bazkidetza kudeatu" }, "premiumManageAlert": { - "message": "You can manage your membership on the bitwarden.com web vault. Do you want to visit the website now?" + "message": "Zure bazkidetza bitwarden.com webguneko kutxa gotorrean kudeatu dezakezu. Orain bisitatu nahi duzu webgunea?" }, "premiumRefresh": { - "message": "Refresh Membership" + "message": "Eguneratu bazkidetza" }, "premiumNotCurrentMember": { - "message": "You are not currently a premium member." + "message": "Orain ez zara premium bazkide." }, "premiumSignUpAndGet": { - "message": "Sign up for a premium membership and get:" + "message": "Erregistra zaitez premium bazkide gisa eta honakoa lortu:" }, "premiumSignUpStorage": { - "message": "1 GB encrypted storage for file attachments." + "message": "Eranskinentzako 1GB-eko zifratutako biltegia." }, "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "message": "YubiKey, FIDO U2F eta Duo bezalako bi urratseko saio hasierarako aukera gehigarriak." }, "premiumSignUpReports": { - "message": "Password hygiene, account health, and data breach reports to keep your vault safe." + "message": "Pasahitzaren higienea, kontuaren egoera eta datu-bortxaketen txostenak, kutxa gotorra seguru mantentzeko." }, "premiumSignUpTotp": { - "message": "TOTP verification code (2FA) generator for logins in your vault." + "message": "TOTP (2FA) egiaztatze-kode sortzailea gotor kutxako erregistroetarako." }, "premiumSignUpSupport": { - "message": "Priority customer support." + "message": "Lehentasunezko bezeroarentzako arreta." }, "premiumSignUpFuture": { - "message": "All future premium features. More coming soon!" + "message": "Etorkizuneko premium ezaugarri guztiak. Laister gehiago!" }, "premiumPurchase": { - "message": "Purchase Premium" + "message": "Premium erosi" }, "premiumPurchaseAlert": { - "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" + "message": "Zure premium bazkidetza bitwarden.com webguneko kutxa gotorrean ordaindu dezakezu. Orain bisitatu nahi duzu webgunea?" }, "premiumCurrentMember": { - "message": "You are a premium member!" + "message": "Premium bazkide zara!" }, "premiumCurrentMemberThanks": { - "message": "Thank you for supporting Bitwarden." + "message": "Eskerrik asko Bitwarden babesteagatik." }, "premiumPrice": { - "message": "All for just $PRICE$ /year!", + "message": "Dena, urtean $PRICE$gatik!", "placeholders": { "price": { "content": "$1", @@ -1101,84 +1101,84 @@ } }, "refreshComplete": { - "message": "Refresh complete" + "message": "Eguneratzea eginda" }, "passwordHistory": { - "message": "Password History" + "message": "Pasahitz historia" }, "clear": { - "message": "Clear", + "message": "Ezabatu", "description": "To clear something out. example: To clear browser history." }, "noPasswordsInList": { - "message": "There are no passwords to list." + "message": "Ez dago erakusteko pasahitzik." }, "undo": { - "message": "Undo" + "message": "Desegin" }, "redo": { - "message": "Redo" + "message": "Berregin" }, "cut": { - "message": "Cut", + "message": "Moztu", "description": "Cut to clipboard" }, "paste": { - "message": "Paste", + "message": "Itsatsi", "description": "Paste from clipboard" }, "selectAll": { - "message": "Select All" + "message": "Hautatu guztiak" }, "zoomIn": { - "message": "Zoom In" + "message": "Zooma handitu" }, "zoomOut": { - "message": "Zoom Out" + "message": "Zooma txikitu" }, "resetZoom": { - "message": "Reset Zoom" + "message": "Leheneratu zooma" }, "toggleFullScreen": { - "message": "Toggle Full Screen" + "message": "Pantaila osoko txandaketa" }, "reload": { - "message": "Reload" + "message": "Birkargatu" }, "toggleDevTools": { - "message": "Toggle Developer Tools" + "message": "Garatzaileen tresnak trandakatu" }, "minimize": { - "message": "Minimize", + "message": "Minimizatu", "description": "Minimize window" }, "zoom": { - "message": "Zoom" + "message": "Zooma" }, "bringAllToFront": { - "message": "Bring All to Front", + "message": "Ekarri denak aurrera", "description": "Bring all windows to front (foreground)" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Bitwarden-i buruz" }, "services": { - "message": "Services" + "message": "Zerbitzuak" }, "hideBitwarden": { - "message": "Hide Bitwarden" + "message": "Ezkutatu Bitwarden" }, "hideOthers": { - "message": "Hide Others" + "message": "Ezkutatu besteak" }, "showAll": { - "message": "Show All" + "message": "Dena erakutsi" }, "quitBitwarden": { - "message": "Quit Bitwarden" + "message": "Kendu Bitwarden" }, "valueCopied": { - "message": "$VALUE$ copied", + "message": "$VALUE$ kopiatuta", "description": "Value has been copied to the clipboard.", "placeholders": { "value": { @@ -1188,16 +1188,16 @@ } }, "help": { - "message": "Help" + "message": "Laguntza" }, "window": { - "message": "Window" + "message": "Leihoa" }, "checkPassword": { - "message": "Check if password has been exposed." + "message": "Egiaztatu pasahitza konprometituta dagoen." }, "passwordExposed": { - "message": "This password has been exposed $VALUE$ time(s) in data breaches. You should change it.", + "message": "Pasahitz hau $VALUE$ aldiz datu-iragazketetan aurkitu da. Aldatu egin beharko zenuke.", "placeholders": { "value": { "content": "$1", @@ -1206,321 +1206,321 @@ } }, "passwordSafe": { - "message": "This password was not found in any known data breaches. It should be safe to use." + "message": "Pasahitz hau ez da inongo datu-filtrazio ezagunetan aurkitu. Erabiltzea segurua izan beharko luke." }, "baseDomain": { - "message": "Base domain", + "message": "Oinarrizko domeinua", "description": "Domain name. Ex. website.com" }, "domainName": { - "message": "Domain Name", + "message": "Domeinu izena", "description": "Domain name. Ex. website.com" }, "host": { - "message": "Host", + "message": "Ostalaria", "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." }, "exact": { - "message": "Exact" + "message": "Zehatza" }, "startsWith": { - "message": "Starts with" + "message": "Hasi honekin" }, "regEx": { - "message": "Regular expression", + "message": "Expresio erregularra", "description": "A programming term, also known as 'RegEx'." }, "matchDetection": { - "message": "Match Detection", + "message": "Detekzio modua", "description": "URI match detection for auto-fill." }, "defaultMatchDetection": { - "message": "Default match detection", + "message": "Lehenetsitako detekzio modua", "description": "Default URI match detection for auto-fill." }, "toggleOptions": { - "message": "Toggle Options" + "message": "Txandaketa aukerak" }, "organization": { - "message": "Organization", + "message": "Erakundea", "description": "An entity of multiple related people (ex. a team or business organization)." }, "default": { - "message": "Default" + "message": "Lehenetsia" }, "exit": { - "message": "Exit" + "message": "Irten" }, "showHide": { - "message": "Show / Hide", + "message": "Erakutsi / Ezkutatu", "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": "Hide to Tray" + "message": "Ezkutatu erretilura" }, "alwaysOnTop": { - "message": "Always on Top", + "message": "Betik gainean", "description": "Application window should always stay on top of other windows" }, "dateUpdated": { - "message": "Updated", + "message": "Eguneratua", "description": "ex. Date this item was updated" }, "datePasswordUpdated": { - "message": "Password Updated", + "message": "Pasahitza eguneratu da", "description": "ex. Date this password was updated" }, "exportVault": { - "message": "Export Vault" + "message": "Esportatu kutxa gotorra" }, "fileFormat": { - "message": "File Format" + "message": "Fitxategiaren formatua" }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" }, "loadAccessibilityCookie": { - "message": "Load Accessibility Cookie" + "message": "Kargatu erabilerraztasun Cookie-ak" }, "registerAccessibilityUser": { - "message": "Register as an accessibility user at", + "message": "Erabilerraztasun erabiltzaile erregistratu", "description": "ex. Register as an accessibility user at hcaptcha.com" }, "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" + "message": "Kopiatu eta itsatsi bidalitako esteka zure emailera" }, "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", + "message": "Sartu hCaptcha helbidearen cookiea kargatzeko URLa", "description": "hCaptcha is the name of a website, should not be translated" }, "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", + "message": "hCaptcha Url behar da", "description": "hCaptcha is the name of a website, should not be translated" }, "invalidUrl": { - "message": "Invalid Url" + "message": "Baliogabeko Url-a" }, "done": { - "message": "Done" + "message": "Egina" }, "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" + "message": "Erabilerraztasun cookie-a gordeta!" }, "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" + "message": "Ez da erabilerraztasun cookie-rik gorde" }, "warning": { - "message": "WARNING", + "message": "KONTUZ", "description": "WARNING (should stay in capitalized letters if the language permits)" }, "confirmVaultExport": { - "message": "Confirm Vault Export" + "message": "Baieztatu kutxa gotorra esportatzea" }, "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": "Esportazio honek kutxa gotorraren datuak zifratu gabeko formatuan biltzen ditu. Ez zenuke gorde edo kanal ez-seguruetaik (emaila, adibidez) bidali behar. Erabili eta berehala ezabatu." }, "encExportKeyWarningDesc": { - "message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file." + "message": "Esportazio honek zure datuak zifratzen ditu zure kontuaren zifratze-gakoa erabiliz. Inoiz zure kontuko zifratze-gakoa berrituz gero, berriro esportatu beharko duzu, ezin izango baituzu fitxategi hori deszifratu." }, "encExportAccountWarningDesc": { - "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." + "message": "Kontua zifratzeko gakoak Bitwarden erabiltzaile bakoitzarentzako bakarrik dira; beraz, ezin da inportatu beste kontu batean zifratutako esportazio bat." }, "noOrganizationsList": { - "message": "You do not belong to any organizations. Organizations allow you to securely share items with other users." + "message": "Zu ez zara inongo erakundekoa. Erakundeek elementuak beste erabiltzaile batzuekin modu seguruan partekatzeko aukera ematen dute." }, "noCollectionsInList": { - "message": "There are no collections to list." + "message": "Ez dago erakusteko bildumarik." }, "ownership": { - "message": "Ownership" + "message": "Jabetza" }, "whoOwnsThisItem": { - "message": "Who owns this item?" + "message": "Nork du elementu hau?" }, "strong": { - "message": "Strong", + "message": "Sendoa", "description": "ex. A strong password. Scale: Weak -> Good -> Strong" }, "good": { - "message": "Good", + "message": "Ona", "description": "ex. A good password. Scale: Weak -> Good -> Strong" }, "weak": { - "message": "Weak", + "message": "Ahula", "description": "ex. A weak password. Scale: Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "Weak Master Password" + "message": "Pasahitz nagusi ahula" }, "weakMasterPasswordDesc": { - "message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?" + "message": "Aukeratu duzun pasahitza ahula da. Pasahitz nagusi sendo bat (edo pasaesaldi bat) erabili beharko zenuke Bitwarden kontua behar bezala babesteko. Ziur zaude pasahitz nagusi hau erabili nahi duzula?" }, "pin": { - "message": "PIN", + "message": "PIN-a", "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "unlockWithPin": { - "message": "Unlock with PIN" + "message": "PIN-arekin desblokeatu" }, "setYourPinCode": { - "message": "Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application." + "message": "Ezarri zure PIN kodea Bitwarden desblokeatzeko. Zure PIN-aren konfigurazioa berrezarriko da, noizbait aplikaziotik erabat saioa ixten baduzu." }, "pinRequired": { - "message": "PIN code is required." + "message": "PIN-a beharrezkoa da." }, "invalidPin": { - "message": "Invalid PIN code." + "message": "PIN baliogabea." }, "unlockWithWindowsHello": { - "message": "Unlock with Windows Hello" + "message": "Desblokeatu Windows Hello-rekin" }, "windowsHelloConsentMessage": { - "message": "Verify for Bitwarden." + "message": "Egiaztatu Bitwarden-entzako." }, "unlockWithTouchId": { - "message": "Unlock with Touch ID" + "message": "Desblokeatu Touch ID-arekin" }, "touchIdConsentMessage": { - "message": "unlock your vault" + "message": "desblokeatu kutxa gotorra" }, "autoPromptWindowsHello": { - "message": "Ask for Windows Hello on launch" + "message": "Eskatu Windows Hello abiaraztean" }, "autoPromptTouchId": { - "message": "Ask for Touch ID on launch" + "message": "Eskatu Touch ID abiaraztean" }, "lockWithMasterPassOnRestart": { - "message": "Lock with master password on restart" + "message": "Berrabiaraztean pasahitz nagusiarekin blokeatu" }, "deleteAccount": { - "message": "Delete account" + "message": "Ezabatu kontua" }, "deleteAccountDesc": { - "message": "Proceed below to delete your account and all vault data." + "message": "Jarraitzeak zure kontua eta kutxa gotorreko datu guztiak ezabatuko ditu." }, "deleteAccountWarning": { - "message": "Deleting your account is permanent. It cannot be undone." + "message": "Zure kontua ezabatzea iraunkorra da. Ezin da desegin." }, "accountDeleted": { - "message": "Account deleted" + "message": "Kontua ezabatua" }, "accountDeletedDesc": { - "message": "Your account has been closed and all associated data has been deleted." + "message": "Zure kontua itxi egin da eta lotutako datu guztiak ezabatu egin dira." }, "preferences": { - "message": "Preferences" + "message": "Hobespenak" }, "enableMenuBar": { - "message": "Show menu bar icon" + "message": "Erakutsi menu-barrako ikonoa" }, "enableMenuBarDesc": { - "message": "Always show an icon in the menu bar." + "message": "Erakutsi ikonoa beti sistemaren menu-barran." }, "hideToMenuBar": { - "message": "Hide to Menu Bar" + "message": "Ezkutatu menu-barrara" }, "selectOneCollection": { - "message": "You must select at least one collection." + "message": "Gutxienez bilduma bat aukeratu behar duzu." }, "premiumUpdated": { - "message": "You've upgraded to premium." + "message": "Premium egin zara." }, "restore": { - "message": "Restore" + "message": "Berreskuratu" }, "premiumManageAlertAppStore": { - "message": "You can manage your subscription from the App Store. Do you want to visit the App Store now?" + "message": "Zure harpidetza App Storetik kudea dezakezu. Bisitatu nahi duzu App Store orain?" }, "legal": { - "message": "Legal", + "message": "Legea", "description": "Noun. As in 'legal documents', like our terms of service and privacy policy." }, "termsOfService": { - "message": "Terms of Service" + "message": "Zerbitzuaren baldintzak" }, "privacyPolicy": { - "message": "Privacy Policy" + "message": "Pribatutasun politika" }, "unsavedChangesConfirmation": { - "message": "Are you sure you want to leave? If you leave now then your current information will not be saved." + "message": "Ziur al daude atera egin nahi duzula? Orain ateratzen bazara, zure uneko informazioa ez da gordeko." }, "unsavedChangesTitle": { - "message": "Unsaved Changes" + "message": "Gorde gabeko aldaketak" }, "clone": { - "message": "Clone" + "message": "Klonatu" }, "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." + "message": "Erakundeko politika batek edo gehiagok sortzailearen konfigurazioari eragiten diote." }, "vaultTimeoutAction": { - "message": "Vault timeout action" + "message": "Kutxa gotorraren itxaronaldiaren ekintza" }, "vaultTimeoutActionLockDesc": { - "message": "Master password or other unlock method is required to access your vault again." + "message": "Pasahitz nagusia edo beste desblokeatze modu bat behar da kutxa gotorrera berriro sartzeko." }, "vaultTimeoutActionLogOutDesc": { - "message": "Re-authentication is required to access your vault again." + "message": "Berriro autentifikatzea beharrezkoa da kutxa gotorrean berriro sartzeko." }, "lock": { - "message": "Lock", + "message": "Blokeatu", "description": "Verb form: to make secure or inaccesible by" }, "trash": { - "message": "Trash", + "message": "Zakarrontzia", "description": "Noun: a special folder to hold deleted items" }, "searchTrash": { - "message": "Search Trash" + "message": "Bilatu zakarrontzian" }, "permanentlyDeleteItem": { - "message": "Permanently Delete Item" + "message": "Ezabatu elementua betirako" }, "permanentlyDeleteItemConfirmation": { - "message": "Are you sure you want to permanently delete this item?" + "message": "Ziur zaude elementu hau betirako ezabatu nahi duzula?" }, "permanentlyDeletedItem": { - "message": "Permanently Deleted item" + "message": "Elementua betirako ezabatua" }, "restoreItem": { - "message": "Restore Item" + "message": "Berreskuratu elementua" }, "restoreItemConfirmation": { - "message": "Are you sure you want to restore this item?" + "message": "Ziur al zaude elementu hau berreskuratu nahi duzula?" }, "restoredItem": { - "message": "Restored Item" + "message": "Elementua berreskuratua" }, "permanentlyDelete": { - "message": "Permanently Delete" + "message": "Ezabatu betiko" }, "vaultTimeoutLogOutConfirmation": { - "message": "Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?" + "message": "Saioa ixteak kutxa gotorreko sarrera guztia kenduko du eta itxaronaldiaren ondoren lineako autentifikazioa eskatuko du. Ziur al zaude hau egin nahi duzula?" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "Timeout Action Confirmation" + "message": "Baieztatu itxaronaldiaren ekintza" }, "enterpriseSingleSignOn": { - "message": "Enterprise Single Sign-On" + "message": "Enpresentzako saio hasiera bakarra" }, "setMasterPassword": { - "message": "Set Master Password" + "message": "Ezarri pasahitz nagusia" }, "ssoCompleteRegistration": { - "message": "In order to complete logging in with SSO, please set a master password to access and protect your vault." + "message": "SSO-rekin saioa hasteko, mesedez, ezarri pasahitz nagusi bat kutxa gotorrera sartu eta babesteko." }, "newMasterPass": { - "message": "New Master Password" + "message": "Pasahitz nagusi berria" }, "confirmNewMasterPass": { - "message": "Confirm New Master Password" + "message": "Pasahitz nagusi berria baieztatu" }, "masterPasswordPolicyInEffect": { - "message": "One or more organization policies require your master password to meet the following requirements:" + "message": "Erakundeko politika batek edo gehiagok pasahitz nagusia behar dute baldintza hauek betetzeko:" }, "policyInEffectMinComplexity": { - "message": "Minimum complexity score of $SCORE$", + "message": "$SCORE$-en gutxieneko konplexutasun puntuazioa", "placeholders": { "score": { "content": "$1", @@ -1529,7 +1529,7 @@ } }, "policyInEffectMinLength": { - "message": "Minimum length of $LENGTH$", + "message": "$LENGTH$-en gutxieneko luzera", "placeholders": { "length": { "content": "$1", @@ -1538,16 +1538,16 @@ } }, "policyInEffectUppercase": { - "message": "Contain one or more uppercase characters" + "message": "Karaktere larri bat edo gehiago edukitzea" }, "policyInEffectLowercase": { - "message": "Contain one or more lowercase characters" + "message": "Karaktere txiki bat edo gehiago edukitzea" }, "policyInEffectNumbers": { - "message": "Contain one or more numbers" + "message": "Zenbaki bat edo gehiago edukitzea" }, "policyInEffectSpecial": { - "message": "Contain one or more of the following special characters $CHARS$", + "message": "Karaktere berezi hauetako $CHARS$ bat edo gehiago edukitzea", "placeholders": { "chars": { "content": "$1", @@ -1556,166 +1556,166 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "Your new master password does not meet the policy requirements." + "message": "Zure pasahitz nagusi berriak ez ditu baldintzak betetzen." }, "acceptPolicies": { - "message": "By checking this box you agree to the following:" + "message": "Laukitxo hau markatzean, honakoa onartzen duzu:" }, "acceptPoliciesRequired": { - "message": "Terms of Service and Privacy Policy have not been acknowledged." + "message": "Zerbitzuaren baldintzak eta pribatutasun politika ez dira onartu." }, "enableBrowserIntegration": { - "message": "Allow browser integration" + "message": "Baimendu nabigatzailearekin integratzea" }, "enableBrowserIntegrationDesc": { - "message": "Used for biometrics in browser." + "message": "Nabigatzailean biometria erabiltzen da." }, "browserIntegrationUnsupportedTitle": { - "message": "Browser integration not supported" + "message": "Ez da nabigatzailearen integrazioa onartzen" }, "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." + "message": "Zoritxarrez, Mac App Storeren bertsioan soilik onartzen da oraingoz nabigatzailearen integrazioa." }, "browserIntegrationWindowsStoreDesc": { - "message": "Unfortunately browser integration is currently not supported in the Windows Store version." + "message": "Zoritxarrez, nabigatzailearen integrazioa ez da onartzen Windows Storen bertsioan." }, "browserIntegrationLinuxDesc": { - "message": "Unfortunately browser integration is currently not supported in the linux version." + "message": "Zoritxarrez, nabigatzailearen integrazioa ez da onartzen Linux bertsioan." }, "enableBrowserIntegrationFingerprint": { - "message": "Require verification for browser integration" + "message": "Nabigatzailea integratzeko egiaztatzea eskatu" }, "enableBrowserIntegrationFingerprintDesc": { - "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." + "message": "Gehitu segurtasun geruza gehigarri bat, hatz-marka berretsi behar baita mahaigainaren eta nabigatzailearen artean lotura bat ezartzean. Horretarako, konexioa sortzen den bakoitzean, erabiltzailearen ekintza eta egiaztapena behar dira." }, "approve": { - "message": "Approve" + "message": "Onartu" }, "verifyBrowserTitle": { - "message": "Verify browser connection" + "message": "Egiaztatu nabigatzailearen konexioa" }, "verifyBrowserDesc": { - "message": "Please ensure the shown fingerprint is identical to the fingerprint showed in the browser extension." + "message": "Ziurtatu bistaratutako hatz-marka digitala nabigatzailearen gehigarrian agertzen den hatz-marka digitalaren berdina dela." }, "biometricsNotEnabledTitle": { - "message": "Biometrics not enabled" + "message": "Biometria desgaitua" }, "biometricsNotEnabledDesc": { - "message": "Browser biometrics requires desktop biometrics to be enabled in the settings first." + "message": "Nabigatzailearen biometriak lehenik mahaigainaren biometria gaitzeko eskatzen du." }, "personalOwnershipSubmitError": { - "message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections." + "message": "Erakundeko politika bat dela eta, ezin dituzu elementuak zure kutxa gotor pertsonalean gorde. Aldatu jabe aukera erakunde aukera batera, eta aukeratu bilduma erabilgarrien artean." }, "hintEqualsPassword": { - "message": "Your password hint cannot be the same as your password." + "message": "Zure pasahitza ezin da izan zure pasahitzaren pistaren berdina." }, "personalOwnershipPolicyInEffect": { - "message": "An organization policy is affecting your ownership options." + "message": "Erakunde politika batek, jabetza aukerei eragiten die." }, "allSends": { - "message": "All Sends", + "message": "Send guztiak", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeFile": { - "message": "File" + "message": "Fitxategia" }, "sendTypeText": { - "message": "Text" + "message": "Testua" }, "searchSends": { - "message": "Search Sends", + "message": "Send-ak bilatu", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { - "message": "Edit Send", + "message": "Editatu Send-a", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "myVault": { - "message": "My Vault" + "message": "Nire kutxa gotorra" }, "text": { - "message": "Text" + "message": "Testua" }, "deletionDate": { - "message": "Deletion Date" + "message": "Ezabatze data" }, "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", + "message": "Send-a betiko ezabatuko da zehaztutako datan eta orduan.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { - "message": "Expiration Date" + "message": "Iraungitze data" }, "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", + "message": "Hala ezartzen bada, Send honetarako sarbidea zehaztutako egunean eta orduan amaituko da.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCount": { - "message": "Maximum Access Count", + "message": "Sarbide kopuru maximoa", "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "maxAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", + "message": "Hala ezartzen bada, erabiltzaileak ezin izango dira Send honetara sartu gehienezko sarbide kopurura iritsi ondoren.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "currentAccessCount": { - "message": "Current Access Count" + "message": "Uneko sarbide kopurua" }, "disableSend": { - "message": "Disable this Send so that no one can access it.", + "message": "Desgaitu Send hau inor sar ez dadin.", "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": "Nahi izanez gero, pasahitza eskatu erabiltzaileak bidalketa honetara sar daitezen.", "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": "Send honi buruzko ohar pribatuak.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLink": { - "message": "Send link", + "message": "Send esteka", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinkLabel": { - "message": "Send Link", + "message": "Bidali esteka", "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": "Send-era sartzean, ezkutatu testua modu lehenetsian", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Created Send", + "message": "Send-a sortua", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Edited Send", + "message": "Send-a editatua", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { - "message": "Deleted Send", + "message": "Send-a ezabatua", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { - "message": "New Password" + "message": "Pasahitz berria" }, "whatTypeOfSend": { - "message": "What type of Send is this?", + "message": "Zein Send mota da hau?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "Create Send", + "message": "Sortu Send-a", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { - "message": "The text you want to send." + "message": "Bidali nahi duzun testua." }, "sendFileDesc": { - "message": "The file you want to send." + "message": "Bidali nahi duzun fitxategia." }, "days": { - "message": "$DAYS$ days", + "message": "$DAYS$ egun", "placeholders": { "days": { "content": "$1", @@ -1724,86 +1724,86 @@ } }, "oneDay": { - "message": "1 day" + "message": "Egun 1" }, "custom": { - "message": "Custom" + "message": "Pertsonalizatua" }, "deleteSendConfirmation": { - "message": "Are you sure you want to delete this Send?", + "message": "Ziur al zaude Send hau ezabatu nahi duzula?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLinkToClipboard": { - "message": "Copy Send link to clipboard", + "message": "Kopiatu Send esteka arbelean", "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": "Gordetzean kopiatu Send honen esteka arbelean, ondoren partekatzeko." }, "sendDisabled": { - "message": "Send disabled", + "message": "Send-a desgaitua", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "Due to an enterprise policy, you are only able to delete an existing Send.", + "message": "Enpresa-politika baten ondorioz, lehendik dagoen Send-a bakarrik ezaba dezakezu.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copyLink": { - "message": "Copy link" + "message": "Kopiatu esteka" }, "disabled": { - "message": "Disabled" + "message": "Desgaitua" }, "maxAccessCountReached": { - "message": "Max access count reached" + "message": "Sarbide kopuru maximoa gaindituta" }, "expired": { - "message": "Expired" + "message": "Iraungita" }, "pendingDeletion": { - "message": "Pending deletion" + "message": "Ezabatzea egiteke" }, "webAuthnAuthenticate": { - "message": "Authenticate WebAuthn" + "message": "WebAuthn autentifikatu" }, "hideEmail": { - "message": "Hide my email address from recipients." + "message": "Ezkutatu nire emaila hartzaileei." }, "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." + "message": "Erakundeko politika batek edo gehiagok Send-eko aukerei eragiten diote." }, "emailVerificationRequired": { - "message": "Email Verification Required" + "message": "Emailaren egiaztapena behar da" }, "emailVerificationRequiredDesc": { - "message": "You must verify your email to use this feature." + "message": "Emaila egiaztatu behar duzu funtzio hau erabiltzeko." }, "passwordPrompt": { - "message": "Master password re-prompt" + "message": "Berriro eskatu pasahitz nagusia" }, "passwordConfirmation": { - "message": "Master password confirmation" + "message": "Baieztatu pasahitz nagusia" }, "passwordConfirmationDesc": { - "message": "This action is protected. To continue, please re-enter your master password to verify your identity." + "message": "Ekintza hau babestuta dago. Jarraitzeko, mesedez, sartu berriro pasahitz nagusia zure identitatea egiaztatzeko." }, "updatedMasterPassword": { - "message": "Updated Master Password" + "message": "Pasahitz nagusia eguneratuta" }, "updateMasterPassword": { - "message": "Update Master Password" + "message": "Pasahitz nagusia eguneratu" }, "updateMasterPasswordWarning": { - "message": "Your Master Password was recently changed by an administrator in your organization. In order to access the vault, you must update it 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." + "message": "Zure erakundeko administratzaile batek pasahitz nagusia aldatu berri du. Kutxa gotorrera sartzeko, pasahitz nagusia orain eguneratu behar duzu. Beraz, oraingo saiotik atera eta saioa hasteko eskatuko zaizu. Beste gailu batzuetako saio aktiboek ordubete iraun dezakete aktibo." }, "hours": { - "message": "Hours" + "message": "Ordu" }, "minutes": { - "message": "Minutes" + "message": "Minutu" }, "vaultTimeoutPolicyInEffect": { - "message": "Your organization policies are affecting your vault timeout. Maximum allowed Vault Timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)", + "message": "Zure erakundearen politikek zure itxaronaldiari eragiten diote. Itxaronaldiak gehienez ere $HOURS$ ordu eta $MINUTES$ minutu izango ditu", "placeholders": { "hours": { "content": "$1", @@ -1816,31 +1816,31 @@ } }, "vaultTimeoutTooLarge": { - "message": "Your vault timeout exceeds the restrictions set by your organization." + "message": "Zure kutxa gotorreko itxaronaldiak, zure erakundeak ezarritako murrizpenak gainditzen ditu." }, "resetPasswordPolicyAutoEnroll": { - "message": "Automatic Enrollment" + "message": "Izen-emate automatikoa" }, "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": "Erakunde honen enpresa politika baten ondorioz, automatikoki pasahitza berrezartzean izen-emango zaitu. Izen-emateak aukera emango die erakundeko administratzaileei pasahitz nagusia aldatzeko." }, "vaultExportDisabled": { - "message": "Vault Export Disabled" + "message": "Kutxa gotorreko esportazioa desgaituta" }, "personalVaultExportPolicyInEffect": { - "message": "One or more organization policies prevents you from exporting your personal vault." + "message": "Erakundeko politika batek edo gehiagok kutxa gotorra esportatzea galarazten dute." }, "addAccount": { - "message": "Add Account" + "message": "Gehitu kontua" }, "removeMasterPassword": { - "message": "Remove Master Password" + "message": "Ezabatu pasahitz nagusia" }, "removedMasterPassword": { - "message": "Master password removed." + "message": "Pasahitz nagusia ezabatua." }, "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", + "message": "$ORGANIZATION$ ostatatze propioko gako-zerbitzari batekin SSO erabiltzen ari da. Dagoeneko ez da pasahitz nagusirik behar erakunde honetako kideentzat saioa hasteko.", "placeholders": { "organization": { "content": "$1", @@ -1849,34 +1849,34 @@ } }, "leaveOrganization": { - "message": "Leave Organization" + "message": "Utzi erakundea" }, "leaveOrganizationConfirmation": { - "message": "Are you sure you want to leave this organization?" + "message": "Ziur al zaude erakundea utzi nahi duzula?" }, "leftOrganization": { - "message": "You have left the organization." + "message": "Erakundea utzi duzu." }, "ssoKeyConnectorError": { - "message": "Key Connector error: make sure Key Connector is available and working correctly." + "message": "Errore bat gertatu da Key Connector-ekin: ziurtatu Key Connector erabilgarri dagoela eta behar bezala dabilela." }, "lockAllVaults": { - "message": "Lock All Vaults" + "message": "Blokeatu kutxa gotor guztiak" }, "accountLimitReached": { - "message": "No more than 5 accounts may be logged in at the same time." + "message": "Gehienez 5 kontu erregistra daitezke aldi berean." }, "accountPreferences": { - "message": "Preferences" + "message": "Hobespenak" }, "appPreferences": { - "message": "App Settings (All Accounts)" + "message": "Aplikazio ezarpenak (kontu guztiak)" }, "accountSwitcherLimitReached": { - "message": "Account limit reached. Log out of an account to add another." + "message": "Kontu mugara iritsi zara. Kontu baten saioa itxi beste bat gehitzeko." }, "settingsTitle": { - "message": "App settings for $EMAIL$", + "message": "$EMAIL$-arentzako aplikazio ezarpenak", "placeholders": { "email": { "content": "$1", @@ -1885,19 +1885,19 @@ } }, "switchAccount": { - "message": "Switch Account" + "message": "Aldatu kontua" }, "options": { - "message": "Options" + "message": "Aukerak" }, "sessionTimeout": { - "message": "Your session has timed out. Please go back and try logging in again." + "message": "Saioa amaitu da. Mesedez, itzuli eta saiatu berriro saioa hasten." }, "exportingPersonalVaultTitle": { - "message": "Exporting Personal Vault" + "message": "Kutxa gotor pertsonala esportatzen" }, "exportingPersonalVaultDescription": { - "message": "Only the personal vault items associated with $EMAIL$ will be exported. Organization vault items will not be included.", + "message": "$EMAIL$-ekin lotutako kutxa gotor pertsonaleko elementuak bakarrik esportatuko dira. Erakundeko kutxa gotorraren elementuak ez dira sartuko.", "placeholders": { "email": { "content": "$1", @@ -1906,87 +1906,90 @@ } }, "locked": { - "message": "Locked" + "message": "Blokeatuta" }, "unlocked": { - "message": "Unlocked" + "message": "Desblokeatuta" }, "generator": { - "message": "Generator" + "message": "Sortzailea" }, "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" + "message": "Zer sortu nahi duzu?" }, "passwordType": { - "message": "Password Type" + "message": "Pasahitz mota" }, "regenerateUsername": { - "message": "Regenerate Username" + "message": "Berrezarri erabiltzaile izena" }, "generateUsername": { - "message": "Generate Username" + "message": "Sortu erabiltzaile izena" }, "usernameType": { - "message": "Username Type" + "message": "Erabiltzaile izen mota" }, "plusAddressedEmail": { - "message": "Plus Addressed Email", + "message": "Atzizkidun emaila", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "Use your email provider's sub-addressing capabilities." + "message": "Erabili emailaren hornitzailearen azpihelbideratze gaitasunak." }, "catchallEmail": { - "message": "Catch-all Email" + "message": "Harrapatu email guztiak" }, "catchallEmailDesc": { - "message": "Use your domain's configured catch-all inbox." + "message": "Erabili zure domeinuan konfiguratutako sarrerako ontzia." }, "random": { - "message": "Random" + "message": "Ausazkoa" }, "randomWord": { - "message": "Random Word" + "message": "Ausazko hitza" }, "websiteName": { - "message": "Website Name" + "message": "Webgune izena" }, "service": { - "message": "Service" + "message": "Zerbitzua" }, "allVaults": { - "message": "All Vaults" + "message": "Kutxa gotor guztiak" }, "searchOrganization": { - "message": "Search Organization" + "message": "Bilatu erakundeak" }, "searchMyVault": { - "message": "Search My Vault" + "message": "Kutxa Gotorrean bilatu" }, "forwardedEmail": { - "message": "Forwarded Email Alias" + "message": "Emaileko ezizena berbidalia" }, "forwardedEmailDesc": { - "message": "Generate an email alias with an external forwarding service." + "message": "Emaileko ezizen bat sortu kanpoko bidalketa zerbitzu batekin." }, "hostname": { - "message": "Hostname", + "message": "Ostalariaren izena", "description": "Part of a URL." }, "apiAccessToken": { - "message": "API Access Token" + "message": "Token sarbide API-a" }, "apiKey": { - "message": "API Key" + "message": "API Gakoa" + }, + "premiumSubcriptionRequired": { + "message": "Premium harpidetza beharrezkoa da" }, "organizationIsDisabled": { - "message": "Organization is disabled." + "message": "Erakundea desgaituta dago." }, "disabledOrganizationFilterError": { - "message": "Items in disabled Organizations cannot be accessed. Contact your Organization owner for assistance." + "message": "Ezin da sartu desgaitutako erakundeetako elementuetara. Laguntza lortzeko, jarri harremanetan zure erakundearekin." }, "neverLockWarning": { - "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." + "message": "Ziur zaude \"Inoiz ez\" aukera erabili nahi duzula? Zure blokeo aukerak \"Inoiz ez\" bezala konfiguratzeak kutxa gotorraren zifratze-gakoa gailuan gordetzen du. Aukera hau erabiltzen baduzu, gailua behar bezala babestuta duzula ziurtatu behar duzu." }, "cardBrandMir": { "message": "Mir" diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index 14b4157eafb..39fcbdaa7f8 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "کلید API" }, + "premiumSubcriptionRequired": { + "message": "اشتراک پرمیوم نیاز است" + }, "organizationIsDisabled": { "message": "سازمان از کار افتاده است." }, diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index d1d1c71b722..b6c656a4ace 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API-avain" }, + "premiumSubcriptionRequired": { + "message": "Premium-tilaus vaaditaan" + }, "organizationIsDisabled": { "message": "Organisaatio on poistettu käytöstä." }, diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 7cf2aefbaed..2fa3065ba8a 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index ca2887c82d6..e18492558a1 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "Clé d'IPA" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index a5c7c09c6d5..3bd0aa60876 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -401,16 +401,16 @@ "message": "אורך" }, "uppercase": { - "message": "Uppercase (A-Z)" + "message": "אותיות רישיות (A-Z)" }, "lowercase": { - "message": "Lowercase (a-z)" + "message": "אותיות קטנות (a-z)" }, "numbers": { - "message": "Numbers (0-9)" + "message": "מספרים (0-9)" }, "specialCharacters": { - "message": "Special Characters (!@#$%^&*)" + "message": "תווים מיוחדים (!@#$%^&*)" }, "numWords": { "message": "מספר מילים" @@ -539,7 +539,7 @@ "message": "נדרשת חזרה על הסיסמה הראשית." }, "masterPasswordMinlength": { - "message": "Master password must be at least 8 characters long." + "message": "הסיסמת הראשית חייבת להכיל 8 תווים לפחות." }, "masterPassDoesntMatch": { "message": "אימות סיסמה ראשית אינו תואם." @@ -898,10 +898,10 @@ "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "enableFavicon": { - "message": "Show website icons" + "message": "הצג סמלים של אתרי האינטרננט" }, "faviconDesc": { - "message": "Show a recognizable image next to each login." + "message": "בכל נסיון התחברות הצג תמונה לזיהוי." }, "enableMinToTray": { "message": "מזער למגש המערכת" @@ -1277,38 +1277,38 @@ "message": "פורמט קובץ" }, "hCaptchaUrl": { - "message": "hCaptcha Url", + "message": "כתובת אתר hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" }, "loadAccessibilityCookie": { - "message": "Load Accessibility Cookie" + "message": "טען עוגיות נגישות Cookie" }, "registerAccessibilityUser": { - "message": "Register as an accessibility user at", + "message": "הירשם כמשתמש נגישות ב-", "description": "ex. Register as an accessibility user at hcaptcha.com" }, "copyPasteLink": { - "message": "Copy and paste the link sent to your email below" + "message": "Copy והדבק את הקישור sent ל-email שלך למטה" }, "enterhCaptchaUrl": { - "message": "Enter URL to load accessibility cookie for hCaptcha", + "message": "הזן את כתובת האתר לטעינת קובץ Cookie של נגישות עבור hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" }, "hCaptchaUrlRequired": { - "message": "hCaptcha Url is required", + "message": "נדרשת כתובת אתר hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" }, "invalidUrl": { - "message": "Invalid Url" + "message": "כתובת אתר לא חוקית" }, "done": { - "message": "Done" + "message": "בוצע" }, "accessibilityCookieSaved": { - "message": "Accessibility cookie saved!" + "message": "קובץ Cookie הנגישות נשמר!" }, "noAccessibilityCookieSaved": { - "message": "No accessibility cookie saved" + "message": "לא נשמרה קובץ Cookie של נגישות" }, "warning": { "message": "אזהרה", @@ -1385,28 +1385,28 @@ "message": "פתח את הכספת שלך" }, "autoPromptWindowsHello": { - "message": "Ask for Windows Hello on launch" + "message": "הצג בקשה של Windows Hello בעת האתחול" }, "autoPromptTouchId": { - "message": "Ask for Touch ID on launch" + "message": "הצג בקשה של Touch ID בעת האתחול" }, "lockWithMasterPassOnRestart": { "message": "נעל בעזרת הסיסמה הראשית בהפעלה מחדש" }, "deleteAccount": { - "message": "Delete account" + "message": "מחק חשבון" }, "deleteAccountDesc": { - "message": "Proceed below to delete your account and all vault data." + "message": "המשך למטה כדי למחוק את חשבונך ואת כל נתוני הכספת." }, "deleteAccountWarning": { - "message": "Deleting your account is permanent. It cannot be undone." + "message": "מחיקת החשבון שלך היא לצמיתות. אי אפשר לבטל את זה." }, "accountDeleted": { - "message": "Account deleted" + "message": "החשבון נמחק" }, "accountDeletedDesc": { - "message": "Your account has been closed and all associated data has been deleted." + "message": "חשבונך נסגר וכל הנתונים המשויכים נמחקו." }, "preferences": { "message": "העדפות" @@ -1571,7 +1571,7 @@ "message": "אינטגרצייה עם הדפדפן מאפשרת שימוש באמצעיים ביומטריים בדפדפן." }, "browserIntegrationUnsupportedTitle": { - "message": "Browser integration not supported" + "message": "שילוב הדפדפן אינו נתמך" }, "browserIntegrationMasOnlyDesc": { "message": "למצער, אינטגרצייה עם הדפדפן בשלב זה נתמכת רק על ידי גרסת חנות האפליקציות של מקינטוש." @@ -1580,7 +1580,7 @@ "message": "למרבה הצער שילוב הדפדפן אינו נתמך כרגע בגרסת ה-Windows Store." }, "browserIntegrationLinuxDesc": { - "message": "Unfortunately browser integration is currently not supported in the linux version." + "message": "לצערינו בגרסאות ה-Linux תכונת השילוב הדפדפן אינה נתמכת כעת." }, "enableBrowserIntegrationFingerprint": { "message": "דרוש אימות לצורך שילוב הדפדפן" @@ -1858,7 +1858,7 @@ "message": "עזבת את הארגון." }, "ssoKeyConnectorError": { - "message": "Key Connector error: make sure Key Connector is available and working correctly." + "message": "שגיאת בחיבור המפתח: ודא שמחבר המפתח זמין ופועל כצפוי." }, "lockAllVaults": { "message": "נעילת כל הכספות" @@ -1906,10 +1906,10 @@ } }, "locked": { - "message": "Locked" + "message": "נָעוּל" }, "unlocked": { - "message": "Unlocked" + "message": "לא נעול" }, "generator": { "message": "מחולל" @@ -1930,17 +1930,17 @@ "message": "סוג שם משתמש" }, "plusAddressedEmail": { - "message": "Plus Addressed Email", + "message": "דואר אלקטרוני ממוען", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "Use your email provider's sub-addressing capabilities." + "message": "השתמש בתכונת הכתובת החלופית של ספק הדוא\"ל שלך." }, "catchallEmail": { - "message": "Catch-all Email" + "message": "Catch-all הדוא\"ל" }, "catchallEmailDesc": { - "message": "Use your domain's configured catch-all inbox." + "message": "השתמש בתכונת ה-catch-all המוגדרת בדומיין שלך." }, "random": { "message": "אקראי" @@ -1955,38 +1955,41 @@ "message": "שירות" }, "allVaults": { - "message": "All Vaults" + "message": "כל הכספות" }, "searchOrganization": { - "message": "Search Organization" + "message": "חיפוש ארגון" }, "searchMyVault": { - "message": "Search My Vault" + "message": "חפש בכספת שלי" }, "forwardedEmail": { - "message": "Forwarded Email Alias" + "message": "כינוי דוא\"ל להעברה" }, "forwardedEmailDesc": { - "message": "Generate an email alias with an external forwarding service." + "message": "צור כינוי דוא\"ל באמצעות שירות שליחת דוא\"ל חיצוני." }, "hostname": { - "message": "Hostname", + "message": "שם מארח", "description": "Part of a URL." }, "apiAccessToken": { - "message": "API Access Token" + "message": "API מפתח גישה" }, "apiKey": { - "message": "API Key" + "message": "מפתח API" + }, + "premiumSubcriptionRequired": { + "message": "נדרש מנוי Premium" }, "organizationIsDisabled": { - "message": "Organization is disabled." + "message": "תכונת חשבון ארגוני מושבת." }, "disabledOrganizationFilterError": { - "message": "Items in disabled Organizations cannot be accessed. Contact your Organization owner for assistance." + "message": "לא ניתן לגשת לפריטים של ארגונים מושבתים. פנה לבעל הארגון שלך לקבלת סיוע." }, "neverLockWarning": { - "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." + "message": "האם אתה בטוח שברצונך להשתמש באפשרות \"לעולם לא\"? על ידי הגדרת אפשרויות הנעילה ל\"לעולם לא\", מפתח ההצפנה של הכספת שלך יישמר במכשיר שלך. אם אתה משתמש באפשרות זו, ודא שאתה שומר על המכשיר שלך מוגן כראוי." }, "cardBrandMir": { "message": "Mir" diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index cb997f8a1e8..103e5e29fca 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 8aee3bfffc6..ffbef2f441e 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API ključ" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index aeacd69f0a1..8ef78286a8a 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API kulcs" }, + "premiumSubcriptionRequired": { + "message": "Prémium előfizetés szükséges" + }, "organizationIsDisabled": { "message": "A szervezet letiltásra került." }, diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 926962af8bb..1c9bcdbfea6 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index dc12cdc3250..ed3b93fd3e6 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "Chiave API" }, + "premiumSubcriptionRequired": { + "message": "È richiesto un abbonamento Premium" + }, "organizationIsDisabled": { "message": "L'organizzazione è disabilitata." }, diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index bd34762b649..554478bbef5 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API キー" }, + "premiumSubcriptionRequired": { + "message": "プレミアム版が必要です" + }, "organizationIsDisabled": { "message": "組織は無効です。" }, diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 7cf2aefbaed..2fa3065ba8a 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 7cf2aefbaed..2fa3065ba8a 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 24f9f26037b..9abe34e39b8 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 3aba79d98ff..c0ed0040402 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API 키" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index fc70fab419e..83efc18ed69 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API atslēga" }, + "premiumSubcriptionRequired": { + "message": "Nepieciešams Premium abonements" + }, "organizationIsDisabled": { "message": "Apvienība ir atspējota." }, diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index e0c2427d97d..b82d2d22878 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 91a0084834d..8f59bdda26e 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index dc8de1f7d13..4716c5b7f09 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -1394,7 +1394,7 @@ "message": "Lås med hovedpassord ved omstart" }, "deleteAccount": { - "message": "Delete account" + "message": "Slett konto" }, "deleteAccountDesc": { "message": "Proceed below to delete your account and all vault data." @@ -1403,7 +1403,7 @@ "message": "Deleting your account is permanent. It cannot be undone." }, "accountDeleted": { - "message": "Account deleted" + "message": "Konto slettet" }, "accountDeletedDesc": { "message": "Your account has been closed and all associated data has been deleted." @@ -1979,8 +1979,11 @@ "apiKey": { "message": "API-nøkkel" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { - "message": "Organization is disabled." + "message": "Organisasjonen er deaktivert." }, "disabledOrganizationFilterError": { "message": "Items in disabled Organizations cannot be accessed. Contact your Organization owner for assistance." diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 922c68eb688..e66d5644ac4 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium-abonnement vereist" + }, "organizationIsDisabled": { "message": "Organisatie is uitgeschakeld." }, diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index bf6907baf41..20d47b8237f 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 87f82ad918c..6871aeea05d 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "Klucz interfejsu API" }, + "premiumSubcriptionRequired": { + "message": "Wymagana jest subskrypcja Premium" + }, "organizationIsDisabled": { "message": "Organizacja jest wyłączona." }, diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index e199c3f342b..d046a63d58a 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "Chave de API" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 8ce45655fc8..da1c8755a0f 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 6b8a91f5ba4..3e7c8ad194d 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "Cheie API" }, + "premiumSubcriptionRequired": { + "message": "Este necesar un abonament Premium" + }, "organizationIsDisabled": { "message": "Organizația este dezactivată." }, diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 456c79a98b9..6da8c2ac569 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "Ключ API" }, + "premiumSubcriptionRequired": { + "message": "Требуется подписка Премиум" + }, "organizationIsDisabled": { "message": "Организация отключена." }, diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 775fa3c68b7..5aa923b05d0 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 54d46d94964..8e00ce376ec 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API kľúč" }, + "premiumSubcriptionRequired": { + "message": "Vyžaduje sa predplatné Prémium" + }, "organizationIsDisabled": { "message": "Organizácia je vypnutá." }, diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 065dcc239b1..b090983ea25 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 38d37a31729..4b2c1931eaf 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "АПИ Кључ" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Организација је онемогућена." }, diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index ee41fb5c971..5acf3f1f65c 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API-nyckel" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index d35a69cd5b7..4c27a385e01 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API Key" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index c9a6b7d2f63..b4b55cc864c 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API anahtarı" }, + "premiumSubcriptionRequired": { + "message": "Premium abonelik gerekli" + }, "organizationIsDisabled": { "message": "Kuruluş devre dışı." }, diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index 8c0a0cc80b8..eef6c908477 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "Ключ API" }, + "premiumSubcriptionRequired": { + "message": "Необхідна передплата преміум" + }, "organizationIsDisabled": { "message": "Організацію вимкнено." }, diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 7c723f7d9b2..5c73bd0132c 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "Khóa API" }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "organizationIsDisabled": { "message": "Organization is disabled." }, diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 8b84f83f8cf..b1f5e56049b 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API 密钥" }, + "premiumSubcriptionRequired": { + "message": "需要高级版订阅" + }, "organizationIsDisabled": { "message": "组织已被禁用。" }, diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 3882d5a57ca..5f5d5e8caf6 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -1979,6 +1979,9 @@ "apiKey": { "message": "API 金鑰" }, + "premiumSubcriptionRequired": { + "message": "需要進階版訂閲" + }, "organizationIsDisabled": { "message": "組織已停用。" }, From d86b2d76c8d2d40af6619b26b517e6fb55cf78ea Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 12 Aug 2022 11:35:15 +0200 Subject: [PATCH 05/16] Autosync the updated translations (#3291) Co-authored-by: github-actions <> --- apps/web/src/locales/af/messages.json | 3 + apps/web/src/locales/ar/messages.json | 3 + apps/web/src/locales/az/messages.json | 3 + apps/web/src/locales/be/messages.json | 83 ++++++++++++------------ apps/web/src/locales/bg/messages.json | 3 + apps/web/src/locales/bn/messages.json | 3 + apps/web/src/locales/bs/messages.json | 29 +++++---- apps/web/src/locales/ca/messages.json | 3 + apps/web/src/locales/cs/messages.json | 3 + apps/web/src/locales/da/messages.json | 3 + apps/web/src/locales/de/messages.json | 3 + apps/web/src/locales/el/messages.json | 3 + apps/web/src/locales/en_GB/messages.json | 5 +- apps/web/src/locales/en_IN/messages.json | 3 + apps/web/src/locales/eo/messages.json | 5 +- apps/web/src/locales/es/messages.json | 3 + apps/web/src/locales/et/messages.json | 15 +++-- apps/web/src/locales/eu/messages.json | 7 +- apps/web/src/locales/fi/messages.json | 3 + apps/web/src/locales/fil/messages.json | 3 + apps/web/src/locales/fr/messages.json | 3 + apps/web/src/locales/he/messages.json | 3 + apps/web/src/locales/hi/messages.json | 3 + apps/web/src/locales/hr/messages.json | 3 + apps/web/src/locales/hu/messages.json | 3 + apps/web/src/locales/id/messages.json | 3 + apps/web/src/locales/it/messages.json | 3 + apps/web/src/locales/ja/messages.json | 3 + apps/web/src/locales/ka/messages.json | 3 + apps/web/src/locales/km/messages.json | 3 + apps/web/src/locales/kn/messages.json | 3 + apps/web/src/locales/ko/messages.json | 3 + apps/web/src/locales/lv/messages.json | 3 + apps/web/src/locales/ml/messages.json | 3 + apps/web/src/locales/nb/messages.json | 3 + apps/web/src/locales/nl/messages.json | 3 + apps/web/src/locales/nn/messages.json | 3 + apps/web/src/locales/pl/messages.json | 5 +- apps/web/src/locales/pt_BR/messages.json | 3 + apps/web/src/locales/pt_PT/messages.json | 3 + apps/web/src/locales/ro/messages.json | 3 + apps/web/src/locales/ru/messages.json | 7 +- apps/web/src/locales/si/messages.json | 3 + apps/web/src/locales/sk/messages.json | 3 + apps/web/src/locales/sl/messages.json | 3 + apps/web/src/locales/sr/messages.json | 5 +- apps/web/src/locales/sr_CS/messages.json | 3 + apps/web/src/locales/sv/messages.json | 3 + apps/web/src/locales/tr/messages.json | 3 + apps/web/src/locales/uk/messages.json | 5 +- apps/web/src/locales/vi/messages.json | 29 +++++---- apps/web/src/locales/zh_CN/messages.json | 7 +- apps/web/src/locales/zh_TW/messages.json | 5 +- 53 files changed, 243 insertions(+), 84 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 464413825aa..a7a77135e6b 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 91cf69bc8a6..4913df7077a 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index a076d0f0b66..02c75991dd8 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium abunəlik tələb olunur" + }, "scim": { "message": "SCIM Təminatı", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 48c5a226dc4..c0ad33a0106 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -1,6 +1,6 @@ { "pageTitle": { - "message": "$APP_NAME$ вэб-сховішча", + "message": "Вэб-сховішча $APP_NAME$", "description": "The title of the website in the browser window.", "placeholders": { "app_name": { @@ -98,7 +98,7 @@ "message": "Красавік" }, "may": { - "message": "Май" + "message": "Травень" }, "june": { "message": "Чэрвень" @@ -134,7 +134,7 @@ "message": "Пані" }, "dr": { - "message": "Док." + "message": "Доктар" }, "expirationMonth": { "message": "Месяц заканчэння" @@ -143,7 +143,7 @@ "message": "Год заканчэння" }, "authenticatorKeyTotp": { - "message": "Ключ праверкі сапраўднасці (TOTP)" + "message": "Ключ аўтэнтыфікацыі (TOTP)" }, "folder": { "message": "Папка" @@ -164,10 +164,10 @@ "message": "Схавана" }, "cfTypeBoolean": { - "message": "Лагічнае" + "message": "Булева" }, "cfTypeLinked": { - "message": "Linked", + "message": "Звязана", "description": "This describes a field that is 'linked' (related) to another field." }, "remove": { @@ -191,7 +191,7 @@ "description": "Domain name. Ex. website.com" }, "domainName": { - "message": "Domain Name", + "message": "Імя дамена", "description": "Domain name. Ex. website.com" }, "host": { @@ -309,13 +309,13 @@ "message": "Logins" }, "typeCardPlural": { - "message": "Cards" + "message": "Карткі" }, "typeIdentityPlural": { - "message": "Identities" + "message": "Пасведчанні" }, "typeSecureNotePlural": { - "message": "Secure Notes" + "message": "Бяспечныя нататкі" }, "folders": { "message": "Папкі" @@ -333,16 +333,16 @@ "message": "Прозвішча" }, "fullName": { - "message": "Full Name" + "message": "Поўнае імя" }, "address1": { "message": "Радок адрасу 1" }, "address2": { - "message": "Радок адрасу 2" + "message": "Адрас 2" }, "address3": { - "message": "Радок адрасу 3" + "message": "Адрас 3" }, "cityTown": { "message": "Горад / Пасёлак" @@ -385,7 +385,7 @@ "message": "Абагуліць" }, "moveToOrganization": { - "message": "Move to Organization" + "message": "Перамясціць у арганізацыю" }, "valueCopied": { "message": "$VALUE$ скапіяваны(-а)", @@ -422,25 +422,25 @@ "description": "Copy URI to clipboard" }, "me": { - "message": "Me" + "message": "Я" }, "myVault": { "message": "Маё сховішча" }, "allVaults": { - "message": "All Vaults" + "message": "Усе сховішчы" }, "vault": { "message": "Сховішча" }, "vaults": { - "message": "Vaults" + "message": "Сховішчы" }, "vaultItems": { - "message": "Vault Items" + "message": "Элементы сховішча" }, "moveSelectedToOrg": { - "message": "Move Selected to Organization" + "message": "Перамясціць выбранае ў арганізацыю" }, "deleteSelected": { "message": "Выдаліць выбраныя" @@ -452,7 +452,7 @@ "message": "Выбраць усё" }, "unselectAll": { - "message": "Адмяніць выбар" + "message": "Зняць выбар з усіх" }, "launch": { "message": "Запусціць" @@ -488,7 +488,7 @@ "message": "Элемент адрэдагаваны" }, "movedItemToOrg": { - "message": "$ITEMNAME$ moved to $ORGNAME$", + "message": "$ITEMNAME$ перамешчана ў $ORGNAME$", "placeholders": { "itemname": { "content": "$1", @@ -501,7 +501,7 @@ } }, "movedItemsToOrg": { - "message": "Selected items moved to $ORGNAME$", + "message": "Выбраныя элементы перамешчаны ў $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -519,13 +519,13 @@ "message": "Выдаліць далучэнне" }, "deleteItemConfirmation": { - "message": "Вы ўпэўнены, што хочаце выдаліць гэты элемент?" + "message": "Вы ўпэўнены, што хочаце адправіць гэты элемент у сметніцу?" }, "deletedItem": { - "message": "Выдалены элемент" + "message": "Элемент адпраўлены ў сметніцу" }, "deletedItems": { - "message": "Выдаленыя элементы" + "message": "Элементы адпраўлены ў сметніцу" }, "movedItems": { "message": "Перамешчаныя элементы" @@ -573,7 +573,7 @@ "message": "Стварыць уліковы запіс" }, "startTrial": { - "message": "Start Trial" + "message": "Пачаць выпрабавальны перыяд" }, "logIn": { "message": "Увайсці" @@ -582,13 +582,13 @@ "message": "Адправіць" }, "emailAddressDesc": { - "message": "You'll use your email address to log in." + "message": "Адрас электроннай пошты будзе выкарыстоўвацца для ўваходу." }, "yourName": { "message": "Ваша імя" }, "yourNameDesc": { - "message": "What should we call you?" + "message": "Як да вас можна звяртацца?" }, "masterPass": { "message": "Асноўны пароль" @@ -597,7 +597,7 @@ "message": "Асноўны пароль — ключ да вашага бяспечнага сховішча. Ён вельмі важны, таму не забывайце яго. Аднавіць асноўны пароль немагчыма." }, "masterPassImportant": { - "message": "Master passwords cannot be recovered if you forget it!" + "message": "Асноўныя паролі немагчыма будзе аднавіць, калі вы іх забудзеце!" }, "masterPassHintDesc": { "message": "Падказка да асноўнага пароля можа дапамагчы вам яго ўспомніць." @@ -630,13 +630,13 @@ "message": "Памылковы адрас электроннай пошты." }, "masterPasswordRequired": { - "message": "Master password is required." + "message": "Патрабуецца асноўны пароль." }, "confirmMasterPasswordRequired": { - "message": "Master password retype is required." + "message": "Неабходна паўторна ўвесці асноўны пароль." }, "masterPasswordMinLength": { - "message": "Master password must be at least 8 characters long." + "message": "Асноўны пароль павінен змяшчаць прынамсі 8 сімвалаў." }, "masterPassDoesntMatch": { "message": "Асноўныя паролі не супадаюць." @@ -645,7 +645,7 @@ "message": "Ваш уліковы запіс створаны! Вы можаце ўвайсці." }, "trialAccountCreated": { - "message": "Account created successfully." + "message": "Уліковы запіс паспяхова створаны." }, "masterPassSent": { "message": "Мы адправілі вам на электронную пошту падказку для асноўнага пароля." @@ -724,7 +724,7 @@ } }, "verificationCodeEmailSent": { - "message": "Адпраўлены ліст для пацвярджэння $EMAIL$.", + "message": "Ліст з пацвярджэннем адпраўлены на $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -736,16 +736,16 @@ "message": "Запомніць мяне" }, "sendVerificationCodeEmailAgain": { - "message": "Адправіць код пацвярджэння зноў" + "message": "Адправіць код пацвярджэння яшчэ раз" }, "useAnotherTwoStepMethod": { "message": "Выкарыстоўваць іншы метад двухэтапнага ўваходу" }, "insertYubiKey": { - "message": "Устаўце ваш YubiKey ў порт USB вашага камп'ютара, затым націсніце на кнопку." + "message": "Устаўце свой YubiKey ў порт USB камп'ютара, а потым націсніце на кнопку." }, "insertU2f": { - "message": "Устаўце ваш ключ бяспекі ў порт USB вашага камп'ютара. Калі на ім ёсць кнопка, націсніце на яе." + "message": "Устаўце ваш ключ бяспекі ў порт USB камп'ютара. Калі на ім ёсць кнопка, націсніце на яе." }, "loginUnavailable": { "message": "Уваход недаступны" @@ -754,7 +754,7 @@ "message": "У гэтага ўліковага запісу ўключаны двухэтапны ўваход, аднак ні адзін з наладжаных варыянтаў не падтрымліваецца гэтым вэб-браўзерам." }, "noTwoStepProviders2": { - "message": "Выкарыстоўвайце актуальын вэб-браўзар (напрыклад, Chrome) і/або дадайце дадатковыя варыянты праверкі сапраўднасці, якія падтрымліваюцца ў вэб-браўзерах (напрыклад, праграма для праверкі сапраўднасці)." + "message": "Выкарыстоўвайце актуальны вэб-браўзер (напрыклад, Chrome) і/або дадайце іншых правайдараў, якія маюць лепшую падтрымку разнастайных вэб-браўзераў (напрыклад, праграма аўтэнтыфікацыі)." }, "twoStepOptions": { "message": "Параметры двухэтапнага ўваходу" @@ -796,10 +796,10 @@ "message": "FIDO2 WebAuthn" }, "webAuthnDesc": { - "message": "Use any WebAuthn enabled security key to access your account." + "message": "Выкарыстоўвайце любы ключ бяспекі WebAuthn, каб атрымаць доступ да вашага ўліковага запісу." }, "webAuthnMigrated": { - "message": "(Migrated from FIDO)" + "message": "(Перанесена з FIDO)" }, "emailTitle": { "message": "Электронная пошта" @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 087d8cc6bd3..dd0243ee3ee 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Изисква се платен абонамент" + }, "scim": { "message": "Удостоверяване чрез SCIM", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 880cedc2f98..1c1f6c3a3a3 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index fc658300371..c8a1e5e3297 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -164,42 +164,42 @@ "message": "Skriveno" }, "cfTypeBoolean": { - "message": "Boolean" + "message": "Logička varijabla" }, "cfTypeLinked": { - "message": "Linked", + "message": "Povezano sa", "description": "This describes a field that is 'linked' (related) to another field." }, "remove": { - "message": "Remove" + "message": "Uklonite" }, "unassigned": { - "message": "Unassigned" + "message": "Nedodijeljeno" }, "noneFolder": { - "message": "No Folder", + "message": "Nema foldera", "description": "This is the folder for uncategorized items" }, "addFolder": { - "message": "Add Folder" + "message": "Dodajte folder" }, "editFolder": { - "message": "Edit Folder" + "message": "Uredite folder" }, "baseDomain": { - "message": "Base domain", + "message": "Osnovni domen", "description": "Domain name. Ex. website.com" }, "domainName": { - "message": "Domain Name", + "message": "Naziv domena", "description": "Domain name. Ex. website.com" }, "host": { - "message": "Host", + "message": "Domaćin", "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." }, "exact": { - "message": "Exact" + "message": "Tačno" }, "startsWith": { "message": "Starts with" @@ -2185,7 +2185,7 @@ } }, "trialThankYou": { - "message": "Thanks for signing up for Bitwarden for $PLAN$!", + "message": "Hvala što ste se prijavili za Bitwarden za $PLAN$!", "placeholders": { "plan": { "content": "$1", @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Potrebna je Premijum pretplata" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." @@ -5308,6 +5311,6 @@ "message": "Mir" }, "numberOfUsers": { - "message": "Number of users" + "message": "Broj korisnika" } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 86735d5c00e..6a75f410bfa 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Cal una subscripció premium" + }, "scim": { "message": "Aprovisionament SCIM", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index ab03a345fe8..2f28e22ba36 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "Poskytování SCIM", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 5431e1fd3d7..96148639ed9 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium-abonnement kræves" + }, "scim": { "message": "SCIM-provisionering", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 03ea4f77c7c..1fb2b0f3f9e 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium-Abonnement erforderlich" + }, "scim": { "message": "SCIM-Bereitstellung", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 3e67d63dbcd..b3e1aa83c94 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 08c7e23e006..cd3a3f89b1e 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -1088,7 +1088,7 @@ "message": "Account deleted" }, "accountDeletedDesc": { - "message": "Your Bitwarden account and vault data were permanently deleted." + "message": "Your account has been closed and all associated data has been deleted." }, "myAccount": { "message": "My account" @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 96ebf5bd184..ac8fdd50fb0 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 4a50e03723e..2a6e2df5bd9 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." @@ -5308,6 +5311,6 @@ "message": "Mir" }, "numberOfUsers": { - "message": "Number of users" + "message": "Nombro de uzantoj" } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index a3ced1441e9..09e435fc14c 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Se requiere una Suscripción Premium" + }, "scim": { "message": "Aprovisionamiento de SCIM", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 0b75f5c0001..c9be9eaae43 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -5093,19 +5093,19 @@ "message": "Konto seaded" }, "generator": { - "message": "Generator" + "message": "Genereerija" }, "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" + "message": "Mida sa soovid genereerida?" }, "passwordType": { - "message": "Password Type" + "message": "Parooli tüüp" }, "regenerateUsername": { "message": "Genereeri kasutajanimi uuesti" }, "generateUsername": { - "message": "Generate Username" + "message": "Genereeri kasutajanimi" }, "usernameType": { "message": "Kasutajanime tüüp" @@ -5124,14 +5124,14 @@ "message": "Kasuta domeenipõhist kogumisaadressi." }, "random": { - "message": "Random", + "message": "Juhuslik", "description": "Generates domain-based username using random letters" }, "randomWord": { "message": "Juhuslik sõna" }, "service": { - "message": "Service" + "message": "Teenus" }, "unknownCipher": { "message": "Unknown Item, you may need to request permission to access this item." @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index c55c791680d..bb7856aad26 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -645,7 +645,7 @@ "message": "Zure kontua egina dago. Orain saioa has dezakezu." }, "trialAccountCreated": { - "message": "Arrakastaz sortutako kontua." + "message": "Kontua sortu da." }, "masterPassSent": { "message": "Mezu elektroniko bat bidali dizugu zure pasahitz nagusiaren pistarekin." @@ -861,7 +861,7 @@ } }, "verificationCodeTotp": { - "message": "Egiaztatze kodea (TOTP)" + "message": "Egiaztatze-kodea (TOTP)" }, "copyVerificationCode": { "message": "Kopiatu egiaztatze-kodea" @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium harpidetza beharrezkoa da" + }, "scim": { "message": "SCIM hornitzailea", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 4b6d255f2d5..96245b7c8e6 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium-tilaus vaaditaan" + }, "scim": { "message": "SCIM-provisiointi", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 0cb2b4ed8cc..2a7985c998c 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 960e75574d9..aaf012d69cb 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Abonnement Premium requis" + }, "scim": { "message": "Provisionnement SCIM", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index e73076c63c5..15f87343b3f 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index fd986cce5c0..733cc4f3c17 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index b982bf64d3e..92ef50abce3 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 227c8894342..bcbbfefdb3a 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Prémium előfizetés szükséges" + }, "scim": { "message": "SCIM ellátás", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index efd4412f53a..7be98b8d3fc 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index e402ce60393..c323591a690 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "È richiesto un abbonamento Premium" + }, "scim": { "message": "Provisioning SCIM", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index abc4cc835db..6a558dac393 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "プレミアム版が必要です" + }, "scim": { "message": "SCIM プロビジョニング", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 71f3bd7b8f6..0e711752773 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 71f3bd7b8f6..0e711752773 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 586aa93c34b..d88dd908061 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index def865850df..7f9589da98d 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index b37ef74ad7b..c78a0c648c4 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Nepieciešams Premium abonements" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 534ade12cf4..6efc527ed56 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 64c52d95a2e..02b68215695 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index e9de1ce2796..06817dd35f2 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium-abonnement vereist" + }, "scim": { "message": "SCIM-provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 3421e8b41b2..8e3ec769619 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 88ffb952b95..c73c7d1bc99 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -1088,7 +1088,7 @@ "message": "Konto zostało usunięte" }, "accountDeletedDesc": { - "message": "Konto zostało zamknięte i wszystkie powiązane z nim dane zostały usunięte." + "message": "Twoje konto zostało zamknięte i wszystkie powiązane z nim dane zostały skasowane." }, "myAccount": { "message": "Moje konto" @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Wymagana jest subskrypcja Premium" + }, "scim": { "message": "Aprowizacja SCIM", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 6ca9811d501..99ea06d91de 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 9a927c65e42..8b7aa2750dd 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Subscrição premium necessária" + }, "scim": { "message": "Provisionamento de SCIM", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 71e95024d2c..a9fd656bb7d 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Este necesar un abonament Premium" + }, "scim": { "message": "Aprovizionarea SCIM", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index ba9803baf53..42036451734 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -1088,7 +1088,7 @@ "message": "Аккаунт удален" }, "accountDeletedDesc": { - "message": "Ваш аккаунт Bitwarden и данные хранилища были удалены навсегда." + "message": "Ваш аккаунт был закрыт, а все связанные с ним данные удалены." }, "myAccount": { "message": "Мой аккаунт" @@ -1270,7 +1270,7 @@ "message": "У вас есть Премиум" }, "alreadyPremiumFromOrg": { - "message": "У вас уже есть доступ к Премиум, так как вы являетесь членом организации." + "message": "У вас уже есть доступ к Премиум, так как вы член организации." }, "manage": { "message": "Управление" @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Требуется подписка Премиум" + }, "scim": { "message": "Обеспечение SCIM", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index e4e6f171348..e729544ae9a 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 5dea5b9b09f..2877ce65edd 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Vyžaduje sa predplatné Prémium" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 794a003147e..1327d411728 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 347bd1b2763..7d4be0f273e 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -1091,7 +1091,7 @@ "message": "Ваш налог је затворен и сви повезани подаци су избрисани." }, "myAccount": { - "message": "Мој Налог" + "message": "Мој налог" }, "tools": { "message": "Алатке" @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 05722e141dc..a23dc931e44 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 2ae4615c78a..fb480542de2 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index d8dda401a52..751c2cf0a06 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium abonelik gerekli" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 282e858d27c..fa03635cb0e 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -1088,7 +1088,7 @@ "message": "Обліковий запис видалено" }, "accountDeletedDesc": { - "message": "Ваш обліковий запис і дані сховища було остаточно видалено." + "message": "Ваш обліковий запис було закрито і всі пов'язані дані було видалено." }, "myAccount": { "message": "Мій обліковий запис" @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Необхідна передплата преміум" + }, "scim": { "message": "Забезпечення SCIM", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 92e4f8fd8ee..62ca418c349 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -167,7 +167,7 @@ "message": "Boolean" }, "cfTypeLinked": { - "message": "Linked", + "message": "Đã liên kết", "description": "This describes a field that is 'linked' (related) to another field." }, "remove": { @@ -191,7 +191,7 @@ "description": "Domain name. Ex. website.com" }, "domainName": { - "message": "Domain Name", + "message": "Tên miền", "description": "Domain name. Ex. website.com" }, "host": { @@ -306,16 +306,16 @@ "message": "Ghi chú bảo mật" }, "typeLoginPlural": { - "message": "Logins" + "message": "Đăng nhập" }, "typeCardPlural": { - "message": "Cards" + "message": "Thẻ" }, "typeIdentityPlural": { - "message": "Identities" + "message": "Danh tính" }, "typeSecureNotePlural": { - "message": "Secure Notes" + "message": "Ghi chú bảo mật" }, "folders": { "message": "Thư mục" @@ -422,7 +422,7 @@ "description": "Copy URI to clipboard" }, "me": { - "message": "Me" + "message": "Tôi" }, "myVault": { "message": "Kho của tôi" @@ -1704,7 +1704,7 @@ "message": "Your account's credit can be used to make purchases. Any available credit will be automatically applied towards invoices generated for this account." }, "goPremium": { - "message": "Go Premium", + "message": "Nâng cấp tài khoản", "description": "Another way of saying \"Get a premium membership\"" }, "premiumUpdated": { @@ -2239,13 +2239,13 @@ "message": "Default Collection" }, "getHelp": { - "message": "Get Help" + "message": "Nhận trợ giúp" }, "getApps": { - "message": "Get the Apps" + "message": "Tải ứng dụng" }, "loggedInAs": { - "message": "Logged in as" + "message": "Đã đăng nhập là" }, "eventLogs": { "message": "Event Logs" @@ -5090,7 +5090,7 @@ "description": "This is used by screen readers to indicate the organization that is currently being shown to the user." }, "accountSettings": { - "message": "Account Settings" + "message": "Cài đặt tài khoản" }, "generator": { "message": "Generator" @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, "scim": { "message": "SCIM Provisioning", "description": "The text, 'SCIM', is an acronymn and should not be translated." @@ -5308,6 +5311,6 @@ "message": "Mir" }, "numberOfUsers": { - "message": "Number of users" + "message": "Số lượng người dùng" } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index de2afc31f7a..fb27453c850 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -177,7 +177,7 @@ "message": "未分派" }, "noneFolder": { - "message": "默认文件夹", + "message": "无文件夹", "description": "This is the folder for uncategorized items" }, "addFolder": { @@ -1088,7 +1088,7 @@ "message": "账户已删除" }, "accountDeletedDesc": { - "message": "您的 Bitwarden 账户和密码库数据已被永久删除。" + "message": "您的账户已被关闭,所有相关数据也已被删除。" }, "myAccount": { "message": "我的账户" @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "需要高级版订阅" + }, "scim": { "message": "SCIM 配置", "description": "The text, 'SCIM', is an acronymn and should not be translated." diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 79aa15ae02a..58c2f2a7f2b 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -1088,7 +1088,7 @@ "message": "已刪除帳戶" }, "accountDeletedDesc": { - "message": "您的 Bitwarden 帳戶和密碼庫資料已被永久刪除。" + "message": "您的帳戶已經關閉,所有關聯的資料也已經被刪除。" }, "myAccount": { "message": "我的帳戶" @@ -5220,6 +5220,9 @@ } } }, + "premiumSubcriptionRequired": { + "message": "需要進階版訂閲" + }, "scim": { "message": "SCIM 佈建", "description": "The text, 'SCIM', is an acronymn and should not be translated." From f42d9673564df2bda411452c70b3dcb013c0bfac Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Fri, 12 Aug 2022 06:45:50 -0600 Subject: [PATCH 06/16] Update browser_action to action (#3286) Manifest v3 gets rid of browser_action for action --- apps/browser/src/manifest.v3.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 5c04b6847be..10e8382fae5 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -73,7 +73,7 @@ "extension_page": "script-src 'self' ; object-src 'self'" }, "commands": { - "_execute_browser_action": { + "_execute_action": { "suggested_key": { "default": "Ctrl+Shift+Y", "linux": "Ctrl+Shift+U" From 2d3d312cd72d6f8215ae2d4f6991acbdec0549d6 Mon Sep 17 00:00:00 2001 From: Robyn MacCallum Date: Fri, 12 Aug 2022 13:19:39 -0400 Subject: [PATCH 07/16] Send reference data on account creation (#3297) --- .../src/app/accounts/register-form/register-form.component.ts | 3 +++ apps/web/src/app/accounts/register.component.html | 1 + 2 files changed, 4 insertions(+) diff --git a/apps/web/src/app/accounts/register-form/register-form.component.ts b/apps/web/src/app/accounts/register-form/register-form.component.ts index 0ec010e5660..8045ef18f1e 100644 --- a/apps/web/src/app/accounts/register-form/register-form.component.ts +++ b/apps/web/src/app/accounts/register-form/register-form.component.ts @@ -15,6 +15,7 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions"; +import { ReferenceEventRequest } from "@bitwarden/common/models/request/referenceEventRequest"; @Component({ selector: "app-register-form", @@ -23,6 +24,7 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/mas export class RegisterFormComponent extends BaseRegisterComponent { @Input() queryParamEmail: string; @Input() enforcedPolicyOptions: MasterPasswordPolicyOptions; + @Input() referenceDataValue: ReferenceEventRequest; showErrorSummary = false; @@ -59,6 +61,7 @@ export class RegisterFormComponent extends BaseRegisterComponent { async ngOnInit() { await super.ngOnInit(); + this.referenceData = this.referenceDataValue; if (this.queryParamEmail) { this.formGroup.get("email")?.setValue(this.queryParamEmail); diff --git a/apps/web/src/app/accounts/register.component.html b/apps/web/src/app/accounts/register.component.html index 930f6412000..ed492a6bb89 100644 --- a/apps/web/src/app/accounts/register.component.html +++ b/apps/web/src/app/accounts/register.component.html @@ -115,6 +115,7 @@ From 96d5f50c7f971c1c74593c50ecf9440c4f7cc42b Mon Sep 17 00:00:00 2001 From: Robyn MacCallum Date: Mon, 15 Aug 2022 09:44:15 -0400 Subject: [PATCH 08/16] Lock node-ipc version (#3304) * lock node-ipc version * update package-lock * npm i --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c8b5aa61649..47061648a11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -147,7 +147,7 @@ "jest-preset-angular": "^12.1.0", "lint-staged": "^13.0.3", "mini-css-extract-plugin": "^2.4.5", - "node-ipc": "^9.2.1", + "node-ipc": "9.2.1", "pkg": "5.7.0", "postcss": "^8.4.14", "postcss-loader": "^7.0.1", diff --git a/package.json b/package.json index 2da3208df08..31c558d2d69 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "jest-preset-angular": "^12.1.0", "lint-staged": "^13.0.3", "mini-css-extract-plugin": "^2.4.5", - "node-ipc": "^9.2.1", + "node-ipc": "9.2.1", "pkg": "5.7.0", "postcss": "^8.4.14", "postcss-loader": "^7.0.1", From d30701ada7e377b2ed1cd692a93608e156fc8efe Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Tue, 16 Aug 2022 00:08:06 +1000 Subject: [PATCH 09/16] [EC-416] Refactor organization permission checks (#3252) * Replace Permissions enum and helper methods with callbacks * Remove scim feature flag * Check if org has feature enabled as part of canManage checks * Pin jest-mock-extended at v2.0.6 to fix compilation error --- apps/web/config/cloud.json | 3 +- apps/web/config/development.json | 3 +- apps/web/config/qa.json | 3 +- apps/web/config/selfhosted.json | 3 +- .../organization-switcher.component.ts | 4 +- apps/web/src/app/layouts/navbar.component.ts | 6 +- .../guards/org-permissions.guard.spec.ts | 163 ++++++++++++++++++ ...ions.guard.ts => org-permissions.guard.ts} | 17 +- .../layouts/organization-layout.component.ts | 12 +- .../organizations/manage/groups.component.ts | 12 +- .../manage/manage.component.html | 10 +- .../organizations/manage/manage.component.ts | 17 -- .../organizations/manage/people.component.ts | 4 - .../manage/policies.component.ts | 5 - .../organizations/navigation-permissions.ts | 29 ++++ .../organization-routing.module.ts | 88 +++++----- .../navigation-permissions.service.ts | 50 ------ .../org-import-export-routing.module.ts | 12 +- apps/web/src/utils/flags.ts | 1 - bitwarden_license/bit-web/jest.config.js | 15 ++ .../organizations-routing.module.ts | 20 +-- .../guards/provider-permissions.guard.spec.ts | 124 +++++++++++++ ...guard.ts => provider-permissions.guard.ts} | 22 ++- .../providers/guards/provider-type.guard.ts | 26 --- .../app/providers/providers-routing.module.ts | 19 +- .../src/app/providers/providers.module.ts | 5 +- bitwarden_license/bit-web/tsconfig.spec.json | 4 + jest.config.js | 1 + libs/common/src/enums/permissions.ts | 29 ---- libs/common/src/models/domain/organization.ts | 33 +--- package-lock.json | 14 +- package.json | 2 +- 32 files changed, 474 insertions(+), 282 deletions(-) create mode 100644 apps/web/src/app/organizations/guards/org-permissions.guard.spec.ts rename apps/web/src/app/organizations/guards/{permissions.guard.ts => org-permissions.guard.ts} (76%) create mode 100644 apps/web/src/app/organizations/navigation-permissions.ts delete mode 100644 apps/web/src/app/organizations/services/navigation-permissions.service.ts create mode 100644 bitwarden_license/bit-web/jest.config.js create mode 100644 bitwarden_license/bit-web/src/app/providers/guards/provider-permissions.guard.spec.ts rename bitwarden_license/bit-web/src/app/providers/guards/{provider.guard.ts => provider-permissions.guard.ts} (54%) delete mode 100644 bitwarden_license/bit-web/src/app/providers/guards/provider-type.guard.ts create mode 100644 bitwarden_license/bit-web/tsconfig.spec.json delete mode 100644 libs/common/src/enums/permissions.ts diff --git a/apps/web/config/cloud.json b/apps/web/config/cloud.json index 1418276d38a..088b52f6dc9 100644 --- a/apps/web/config/cloud.json +++ b/apps/web/config/cloud.json @@ -16,7 +16,6 @@ "proxyEvents": "https://events.bitwarden.com" }, "flags": { - "showTrial": false, - "scim": true + "showTrial": false } } diff --git a/apps/web/config/development.json b/apps/web/config/development.json index c1e7bbf08f6..e3048db7a22 100644 --- a/apps/web/config/development.json +++ b/apps/web/config/development.json @@ -10,7 +10,6 @@ "proxyNotifications": "http://localhost:61840" }, "flags": { - "showTrial": true, - "scim": true + "showTrial": true } } diff --git a/apps/web/config/qa.json b/apps/web/config/qa.json index 6f7b4135797..4371ea1ff98 100644 --- a/apps/web/config/qa.json +++ b/apps/web/config/qa.json @@ -10,7 +10,6 @@ "proxyEvents": "https://events.qa.bitwarden.pw" }, "flags": { - "showTrial": true, - "scim": true + "showTrial": true } } diff --git a/apps/web/config/selfhosted.json b/apps/web/config/selfhosted.json index 460e87ae84b..3ba61fda596 100644 --- a/apps/web/config/selfhosted.json +++ b/apps/web/config/selfhosted.json @@ -7,7 +7,6 @@ "port": 8081 }, "flags": { - "showTrial": false, - "scim": true + "showTrial": false } } diff --git a/apps/web/src/app/components/organization-switcher.component.ts b/apps/web/src/app/components/organization-switcher.component.ts index 543aa7059b5..a935cd2b895 100644 --- a/apps/web/src/app/components/organization-switcher.component.ts +++ b/apps/web/src/app/components/organization-switcher.component.ts @@ -5,7 +5,7 @@ import { OrganizationService } from "@bitwarden/common/abstractions/organization import { Utils } from "@bitwarden/common/misc/utils"; import { Organization } from "@bitwarden/common/models/domain/organization"; -import { NavigationPermissionsService } from "../organizations/services/navigation-permissions.service"; +import { canAccessOrgAdmin } from "../organizations/navigation-permissions"; @Component({ selector: "app-organization-switcher", @@ -26,7 +26,7 @@ export class OrganizationSwitcherComponent implements OnInit { async load() { const orgs = await this.organizationService.getAll(); this.organizations = orgs - .filter((org) => NavigationPermissionsService.canAccessAdmin(org)) + .filter(canAccessOrgAdmin) .sort(Utils.getSortFunction(this.i18nService, "name")); this.loaded = true; diff --git a/apps/web/src/app/layouts/navbar.component.ts b/apps/web/src/app/layouts/navbar.component.ts index d35929b6b4c..c5a6b03cad5 100644 --- a/apps/web/src/app/layouts/navbar.component.ts +++ b/apps/web/src/app/layouts/navbar.component.ts @@ -12,7 +12,7 @@ import { Utils } from "@bitwarden/common/misc/utils"; import { Organization } from "@bitwarden/common/models/domain/organization"; import { Provider } from "@bitwarden/common/models/domain/provider"; -import { NavigationPermissionsService as OrgNavigationPermissionsService } from "../organizations/services/navigation-permissions.service"; +import { canAccessOrgAdmin } from "../organizations/navigation-permissions"; @Component({ selector: "app-navbar", @@ -69,9 +69,7 @@ export class NavbarComponent implements OnInit { async buildOrganizations() { const allOrgs = await this.organizationService.getAll(); - return allOrgs - .filter((org) => OrgNavigationPermissionsService.canAccessAdmin(org)) - .sort(Utils.getSortFunction(this.i18nService, "name")); + return allOrgs.filter(canAccessOrgAdmin).sort(Utils.getSortFunction(this.i18nService, "name")); } lock() { diff --git a/apps/web/src/app/organizations/guards/org-permissions.guard.spec.ts b/apps/web/src/app/organizations/guards/org-permissions.guard.spec.ts new file mode 100644 index 00000000000..03d91822a52 --- /dev/null +++ b/apps/web/src/app/organizations/guards/org-permissions.guard.spec.ts @@ -0,0 +1,163 @@ +import { + ActivatedRouteSnapshot, + convertToParamMap, + Router, + RouterStateSnapshot, +} from "@angular/router"; +import { mock, MockProxy } from "jest-mock-extended"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { SyncService } from "@bitwarden/common/abstractions/sync.service"; +import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; +import { Organization } from "@bitwarden/common/models/domain/organization"; + +import { OrganizationPermissionsGuard } from "./org-permissions.guard"; + +const orgFactory = (props: Partial = {}) => + Object.assign( + new Organization(), + { + id: "myOrgId", + enabled: true, + type: OrganizationUserType.Admin, + }, + props + ); + +describe("Organization Permissions Guard", () => { + let router: MockProxy; + let organizationService: MockProxy; + let state: MockProxy; + let route: MockProxy; + + let organizationPermissionsGuard: OrganizationPermissionsGuard; + + beforeEach(() => { + router = mock(); + organizationService = mock(); + state = mock(); + route = mock({ + params: { + organizationId: orgFactory().id, + }, + data: { + organizationPermissions: null, + }, + }); + + organizationPermissionsGuard = new OrganizationPermissionsGuard( + router, + organizationService, + mock(), + mock(), + mock() + ); + }); + + it("blocks navigation if organization does not exist", async () => { + organizationService.get.mockResolvedValue(null); + + const actual = await organizationPermissionsGuard.canActivate(route, state); + + expect(actual).not.toBe(true); + }); + + it("permits navigation if no permissions are specified", async () => { + const org = orgFactory(); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await organizationPermissionsGuard.canActivate(route, state); + + expect(actual).toBe(true); + }); + + it("permits navigation if the user has permissions", async () => { + const permissionsCallback = jest.fn(); + permissionsCallback.mockImplementation((org) => true); + route.data = { + organizationPermissions: permissionsCallback, + }; + + const org = orgFactory(); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await organizationPermissionsGuard.canActivate(route, state); + + expect(permissionsCallback).toHaveBeenCalled(); + expect(actual).toBe(true); + }); + + describe("if the user does not have permissions", () => { + it("and there is no Item ID, block navigation", async () => { + const permissionsCallback = jest.fn(); + permissionsCallback.mockImplementation((org) => false); + route.data = { + organizationPermissions: permissionsCallback, + }; + + state = mock({ + root: mock({ + queryParamMap: convertToParamMap({}), + }), + }); + + const org = orgFactory(); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await organizationPermissionsGuard.canActivate(route, state); + + expect(permissionsCallback).toHaveBeenCalled(); + expect(actual).not.toBe(true); + }); + + it("and there is an Item ID, redirect to the item in the individual vault", async () => { + route.data = { + organizationPermissions: (org: Organization) => false, + }; + state = mock({ + root: mock({ + queryParamMap: convertToParamMap({ + itemId: "myItemId", + }), + }), + }); + const org = orgFactory(); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await organizationPermissionsGuard.canActivate(route, state); + + expect(router.createUrlTree).toHaveBeenCalledWith(["/vault"], { + queryParams: { itemId: "myItemId" }, + }); + expect(actual).not.toBe(true); + }); + }); + + describe("given a disabled organization", () => { + it("blocks navigation if user is not an owner", async () => { + const org = orgFactory({ + type: OrganizationUserType.Admin, + enabled: false, + }); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await organizationPermissionsGuard.canActivate(route, state); + + expect(actual).not.toBe(true); + }); + + it("permits navigation if user is an owner", async () => { + const org = orgFactory({ + type: OrganizationUserType.Owner, + enabled: false, + }); + organizationService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await organizationPermissionsGuard.canActivate(route, state); + + expect(actual).toBe(true); + }); + }); +}); diff --git a/apps/web/src/app/organizations/guards/permissions.guard.ts b/apps/web/src/app/organizations/guards/org-permissions.guard.ts similarity index 76% rename from apps/web/src/app/organizations/guards/permissions.guard.ts rename to apps/web/src/app/organizations/guards/org-permissions.guard.ts index 2ced429c76d..31642a44a4b 100644 --- a/apps/web/src/app/organizations/guards/permissions.guard.ts +++ b/apps/web/src/app/organizations/guards/org-permissions.guard.ts @@ -5,12 +5,14 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { SyncService } from "@bitwarden/common/abstractions/sync.service"; -import { Permissions } from "@bitwarden/common/enums/permissions"; +import { Organization } from "@bitwarden/common/models/domain/organization"; + +import { canAccessOrgAdmin } from "../navigation-permissions"; @Injectable({ providedIn: "root", }) -export class PermissionsGuard implements CanActivate { +export class OrganizationPermissionsGuard implements CanActivate { constructor( private router: Router, private organizationService: OrganizationService, @@ -39,8 +41,11 @@ export class PermissionsGuard implements CanActivate { return this.router.createUrlTree(["/"]); } - const permissions = route.data == null ? [] : (route.data.permissions as Permissions[]); - if (permissions != null && !org.hasAnyPermission(permissions)) { + const permissionsCallback: (organization: Organization) => boolean = + route.data?.organizationPermissions; + const hasPermissions = permissionsCallback == null || permissionsCallback(org); + + if (!hasPermissions) { // Handle linkable ciphers for organizations the user only has view access to // https://bitwarden.atlassian.net/browse/EC-203 const cipherId = @@ -54,7 +59,9 @@ export class PermissionsGuard implements CanActivate { } this.platformUtilsService.showToast("error", null, this.i18nService.t("accessDenied")); - return this.router.createUrlTree(["/"]); + return canAccessOrgAdmin(org) + ? this.router.createUrlTree(["/organizations", org.id]) + : this.router.createUrlTree(["/"]); } return true; diff --git a/apps/web/src/app/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/organizations/layouts/organization-layout.component.ts index c9dcbd5cf3c..d5a0a668da2 100644 --- a/apps/web/src/app/organizations/layouts/organization-layout.component.ts +++ b/apps/web/src/app/organizations/layouts/organization-layout.component.ts @@ -5,7 +5,11 @@ import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.s import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; import { Organization } from "@bitwarden/common/models/domain/organization"; -import { NavigationPermissionsService } from "../services/navigation-permissions.service"; +import { + canAccessManageTab, + canAccessSettingsTab, + canAccessToolsTab, +} from "../navigation-permissions"; const BroadcasterSubscriptionId = "OrganizationLayoutComponent"; @@ -51,15 +55,15 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy { } get showManageTab(): boolean { - return NavigationPermissionsService.canAccessManage(this.organization); + return canAccessManageTab(this.organization); } get showToolsTab(): boolean { - return NavigationPermissionsService.canAccessTools(this.organization); + return canAccessToolsTab(this.organization); } get showSettingsTab(): boolean { - return NavigationPermissionsService.canAccessSettings(this.organization); + return canAccessSettingsTab(this.organization); } get toolsRoute(): string { diff --git a/apps/web/src/app/organizations/manage/groups.component.ts b/apps/web/src/app/organizations/manage/groups.component.ts index fbf009762c1..d09acfae30f 100644 --- a/apps/web/src/app/organizations/manage/groups.component.ts +++ b/apps/web/src/app/organizations/manage/groups.component.ts @@ -1,12 +1,11 @@ import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; +import { ActivatedRoute } from "@angular/router"; import { first } from "rxjs/operators"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; -import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { Utils } from "@bitwarden/common/misc/utils"; @@ -41,20 +40,13 @@ export class GroupsComponent implements OnInit { private i18nService: I18nService, private modalService: ModalService, private platformUtilsService: PlatformUtilsService, - private router: Router, private searchService: SearchService, - private logService: LogService, - private organizationService: OrganizationService + private logService: LogService ) {} async ngOnInit() { this.route.parent.parent.params.subscribe(async (params) => { this.organizationId = params.organizationId; - const organization = await this.organizationService.get(this.organizationId); - if (organization == null || !organization.useGroups) { - this.router.navigate(["/organizations", this.organizationId]); - return; - } await this.load(); this.route.queryParams.pipe(first()).subscribe(async (qParams) => { this.searchText = qParams.search; diff --git a/apps/web/src/app/organizations/manage/manage.component.html b/apps/web/src/app/organizations/manage/manage.component.html index 5d119bd4bb4..b96b8b0859b 100644 --- a/apps/web/src/app/organizations/manage/manage.component.html +++ b/apps/web/src/app/organizations/manage/manage.component.html @@ -24,7 +24,7 @@ routerLink="groups" class="list-group-item" routerLinkActive="active" - *ngIf="organization.canManageGroups && accessGroups" + *ngIf="organization.canManageGroups" > {{ "groups" | i18n }} @@ -32,7 +32,7 @@ routerLink="policies" class="list-group-item" routerLinkActive="active" - *ngIf="organization.canManagePolicies && accessPolicies" + *ngIf="organization.canManagePolicies" > {{ "policies" | i18n }} @@ -40,7 +40,7 @@ routerLink="sso" class="list-group-item" routerLinkActive="active" - *ngIf="organization.canManageSso && accessSso" + *ngIf="organization.canManageSso" > {{ "singleSignOn" | i18n }} @@ -48,7 +48,7 @@ routerLink="scim" class="list-group-item" routerLinkActive="active" - *ngIf="organization.canManageScim && accessScim" + *ngIf="organization.canManageScim" > {{ "scim" | i18n }} @@ -56,7 +56,7 @@ routerLink="events" class="list-group-item" routerLinkActive="active" - *ngIf="organization.canAccessEventLogs && accessEvents" + *ngIf="organization.canAccessEventLogs" > {{ "eventLogs" | i18n }} diff --git a/apps/web/src/app/organizations/manage/manage.component.ts b/apps/web/src/app/organizations/manage/manage.component.ts index 85ba5110212..f9026f55707 100644 --- a/apps/web/src/app/organizations/manage/manage.component.ts +++ b/apps/web/src/app/organizations/manage/manage.component.ts @@ -4,35 +4,18 @@ import { ActivatedRoute } from "@angular/router"; import { OrganizationService } from "@bitwarden/common/abstractions/organization.service"; import { Organization } from "@bitwarden/common/models/domain/organization"; -import { flagEnabled } from "../../../utils/flags"; - @Component({ selector: "app-org-manage", templateUrl: "manage.component.html", }) export class ManageComponent implements OnInit { organization: Organization; - accessPolicies = false; - accessGroups = false; - accessEvents = false; - accessSso = false; - accessScim = false; constructor(private route: ActivatedRoute, private organizationService: OrganizationService) {} ngOnInit() { this.route.parent.params.subscribe(async (params) => { this.organization = await this.organizationService.get(params.organizationId); - this.accessPolicies = this.organization.usePolicies; - this.accessSso = this.organization.useSso; - this.accessEvents = this.organization.useEvents; - this.accessGroups = this.organization.useGroups; - - if (flagEnabled("scim")) { - this.accessScim = this.organization.useScim; - } else { - this.accessScim = false; - } }); } } diff --git a/apps/web/src/app/organizations/manage/people.component.ts b/apps/web/src/app/organizations/manage/people.component.ts index 1763e9d414e..cca25d4dfdf 100644 --- a/apps/web/src/app/organizations/manage/people.component.ts +++ b/apps/web/src/app/organizations/manage/people.component.ts @@ -113,10 +113,6 @@ export class PeopleComponent this.route.parent.parent.params.subscribe(async (params) => { this.organizationId = params.organizationId; const organization = await this.organizationService.get(this.organizationId); - if (!organization.canManageUsers) { - this.router.navigate(["../collections"], { relativeTo: this.route }); - return; - } this.accessEvents = organization.useEvents; this.accessGroups = organization.useGroups; this.canResetPassword = organization.canManageUsersPassword; diff --git a/apps/web/src/app/organizations/manage/policies.component.ts b/apps/web/src/app/organizations/manage/policies.component.ts index dde0c3def43..17cc3b7bbe6 100644 --- a/apps/web/src/app/organizations/manage/policies.component.ts +++ b/apps/web/src/app/organizations/manage/policies.component.ts @@ -43,11 +43,6 @@ export class PoliciesComponent implements OnInit { this.route.parent.parent.params.subscribe(async (params) => { this.organizationId = params.organizationId; this.organization = await this.organizationService.get(this.organizationId); - if (this.organization == null || !this.organization.usePolicies) { - this.router.navigate(["/organizations", this.organizationId]); - return; - } - this.policies = this.policyListService.getPolicies(); await this.load(); diff --git a/apps/web/src/app/organizations/navigation-permissions.ts b/apps/web/src/app/organizations/navigation-permissions.ts new file mode 100644 index 00000000000..903efbdee2e --- /dev/null +++ b/apps/web/src/app/organizations/navigation-permissions.ts @@ -0,0 +1,29 @@ +import { Organization } from "@bitwarden/common/models/domain/organization"; + +export function canAccessToolsTab(org: Organization): boolean { + return org.canAccessImportExport || org.canAccessReports; +} + +export function canAccessSettingsTab(org: Organization): boolean { + return org.isOwner; +} + +export function canAccessManageTab(org: Organization): boolean { + return ( + org.canCreateNewCollections || + org.canEditAnyCollection || + org.canDeleteAnyCollection || + org.canEditAssignedCollections || + org.canDeleteAssignedCollections || + org.canAccessEventLogs || + org.canManageGroups || + org.canManageUsers || + org.canManagePolicies || + org.canManageSso || + org.canManageScim + ); +} + +export function canAccessOrgAdmin(org: Organization): boolean { + return canAccessToolsTab(org) || canAccessSettingsTab(org) || canAccessManageTab(org); +} diff --git a/apps/web/src/app/organizations/organization-routing.module.ts b/apps/web/src/app/organizations/organization-routing.module.ts index d820ed09bda..5ce58d17538 100644 --- a/apps/web/src/app/organizations/organization-routing.module.ts +++ b/apps/web/src/app/organizations/organization-routing.module.ts @@ -2,9 +2,9 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "@bitwarden/angular/guards/auth.guard"; -import { Permissions } from "@bitwarden/common/enums/permissions"; +import { Organization } from "@bitwarden/common/models/domain/organization"; -import { PermissionsGuard } from "./guards/permissions.guard"; +import { OrganizationPermissionsGuard } from "./guards/org-permissions.guard"; import { OrganizationLayoutComponent } from "./layouts/organization-layout.component"; import { CollectionsComponent } from "./manage/collections.component"; import { EventsComponent } from "./manage/events.component"; @@ -12,7 +12,12 @@ import { GroupsComponent } from "./manage/groups.component"; import { ManageComponent } from "./manage/manage.component"; import { PeopleComponent } from "./manage/people.component"; import { PoliciesComponent } from "./manage/policies.component"; -import { NavigationPermissionsService } from "./services/navigation-permissions.service"; +import { + canAccessOrgAdmin, + canAccessManageTab, + canAccessSettingsTab, + canAccessToolsTab, +} from "./navigation-permissions"; import { AccountComponent } from "./settings/account.component"; import { OrganizationBillingComponent } from "./settings/organization-billing.component"; import { OrganizationSubscriptionComponent } from "./settings/organization-subscription.component"; @@ -30,9 +35,9 @@ const routes: Routes = [ { path: ":organizationId", component: OrganizationLayoutComponent, - canActivate: [AuthGuard, PermissionsGuard], + canActivate: [AuthGuard, OrganizationPermissionsGuard], data: { - permissions: NavigationPermissionsService.getPermissions("admin"), + organizationPermissions: canAccessOrgAdmin, }, children: [ { path: "", pathMatch: "full", redirectTo: "vault" }, @@ -43,8 +48,10 @@ const routes: Routes = [ { path: "tools", component: ToolsComponent, - canActivate: [PermissionsGuard], - data: { permissions: NavigationPermissionsService.getPermissions("tools") }, + canActivate: [OrganizationPermissionsGuard], + data: { + organizationPermissions: canAccessToolsTab, + }, children: [ { path: "", @@ -61,46 +68,46 @@ const routes: Routes = [ { path: "exposed-passwords-report", component: ExposedPasswordsReportComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "exposedPasswordsReport", - permissions: [Permissions.AccessReports], + organizationPermissions: (org: Organization) => org.canAccessReports, }, }, { path: "inactive-two-factor-report", component: InactiveTwoFactorReportComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "inactive2faReport", - permissions: [Permissions.AccessReports], + organizationPermissions: (org: Organization) => org.canAccessReports, }, }, { path: "reused-passwords-report", component: ReusedPasswordsReportComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "reusedPasswordsReport", - permissions: [Permissions.AccessReports], + organizationPermissions: (org: Organization) => org.canAccessReports, }, }, { path: "unsecured-websites-report", component: UnsecuredWebsitesReportComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "unsecuredWebsitesReport", - permissions: [Permissions.AccessReports], + organizationPermissions: (org: Organization) => org.canAccessReports, }, }, { path: "weak-passwords-report", component: WeakPasswordsReportComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "weakPasswordsReport", - permissions: [Permissions.AccessReports], + organizationPermissions: (org: Organization) => org.canAccessReports, }, }, ], @@ -108,9 +115,9 @@ const routes: Routes = [ { path: "manage", component: ManageComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { - permissions: NavigationPermissionsService.getPermissions("manage"), + organizationPermissions: canAccessManageTab, }, children: [ { @@ -121,52 +128,52 @@ const routes: Routes = [ { path: "collections", component: CollectionsComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "collections", - permissions: [ - Permissions.CreateNewCollections, - Permissions.EditAnyCollection, - Permissions.DeleteAnyCollection, - Permissions.EditAssignedCollections, - Permissions.DeleteAssignedCollections, - ], + organizationPermissions: (org: Organization) => + org.canCreateNewCollections || + org.canEditAnyCollection || + org.canDeleteAnyCollection || + org.canEditAssignedCollections || + org.canDeleteAssignedCollections, }, }, { path: "events", component: EventsComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "eventLogs", - permissions: [Permissions.AccessEventLogs], + organizationPermissions: (org: Organization) => org.canAccessEventLogs, }, }, { path: "groups", component: GroupsComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "groups", - permissions: [Permissions.ManageGroups], + organizationPermissions: (org: Organization) => org.canManageGroups, }, }, { path: "people", component: PeopleComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "people", - permissions: [Permissions.ManageUsers, Permissions.ManageUsersPassword], + organizationPermissions: (org: Organization) => + org.canManageUsers || org.canManageUsersPassword, }, }, { path: "policies", component: PoliciesComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "policies", - permissions: [Permissions.ManagePolicies], + organizationPermissions: (org: Organization) => org.canManagePolicies, }, }, ], @@ -174,8 +181,8 @@ const routes: Routes = [ { path: "settings", component: SettingsComponent, - canActivate: [PermissionsGuard], - data: { permissions: NavigationPermissionsService.getPermissions("settings") }, + canActivate: [OrganizationPermissionsGuard], + data: { organizationPermissions: canAccessSettingsTab }, children: [ { path: "", pathMatch: "full", redirectTo: "account" }, { path: "account", component: AccountComponent, data: { titleId: "myOrganization" } }, @@ -187,8 +194,11 @@ const routes: Routes = [ { path: "billing", component: OrganizationBillingComponent, - canActivate: [PermissionsGuard], - data: { titleId: "billing", permissions: [Permissions.ManageBilling] }, + canActivate: [OrganizationPermissionsGuard], + data: { + titleId: "billing", + organizationPermissions: (org: Organization) => org.canManageBilling, + }, }, { path: "subscription", diff --git a/apps/web/src/app/organizations/services/navigation-permissions.service.ts b/apps/web/src/app/organizations/services/navigation-permissions.service.ts deleted file mode 100644 index cddd480cebb..00000000000 --- a/apps/web/src/app/organizations/services/navigation-permissions.service.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Permissions } from "@bitwarden/common/enums/permissions"; -import { Organization } from "@bitwarden/common/models/domain/organization"; - -const permissions = { - manage: [ - Permissions.CreateNewCollections, - Permissions.EditAnyCollection, - Permissions.DeleteAnyCollection, - Permissions.EditAssignedCollections, - Permissions.DeleteAssignedCollections, - Permissions.AccessEventLogs, - Permissions.ManageGroups, - Permissions.ManageUsers, - Permissions.ManagePolicies, - Permissions.ManageSso, - Permissions.ManageScim, - ], - tools: [Permissions.AccessImportExport, Permissions.AccessReports], - settings: [Permissions.ManageOrganization], -}; - -export class NavigationPermissionsService { - static getPermissions(route: keyof typeof permissions | "admin") { - if (route === "admin") { - return Object.values(permissions).reduce((previous, current) => previous.concat(current), []); - } - - return permissions[route]; - } - - static canAccessAdmin(organization: Organization): boolean { - return ( - this.canAccessTools(organization) || - this.canAccessSettings(organization) || - this.canAccessManage(organization) - ); - } - - static canAccessTools(organization: Organization): boolean { - return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("tools")); - } - - static canAccessSettings(organization: Organization): boolean { - return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("settings")); - } - - static canAccessManage(organization: Organization): boolean { - return organization.hasAnyPermission(NavigationPermissionsService.getPermissions("manage")); - } -} diff --git a/apps/web/src/app/organizations/tools/import-export/org-import-export-routing.module.ts b/apps/web/src/app/organizations/tools/import-export/org-import-export-routing.module.ts index 8570a066ee6..145afc3cb75 100644 --- a/apps/web/src/app/organizations/tools/import-export/org-import-export-routing.module.ts +++ b/apps/web/src/app/organizations/tools/import-export/org-import-export-routing.module.ts @@ -1,9 +1,9 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; -import { Permissions } from "@bitwarden/common/enums/permissions"; +import { Organization } from "@bitwarden/common/models/domain/organization"; -import { PermissionsGuard } from "../../guards/permissions.guard"; +import { OrganizationPermissionsGuard } from "../../guards/org-permissions.guard"; import { OrganizationExportComponent } from "./org-export.component"; import { OrganizationImportComponent } from "./org-import.component"; @@ -12,19 +12,19 @@ const routes: Routes = [ { path: "import", component: OrganizationImportComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "importData", - permissions: [Permissions.AccessImportExport], + organizationPermissions: (org: Organization) => org.canAccessImportExport, }, }, { path: "export", component: OrganizationExportComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { titleId: "exportVault", - permissions: [Permissions.AccessImportExport], + organizationPermissions: (org: Organization) => org.canAccessImportExport, }, }, ]; diff --git a/apps/web/src/utils/flags.ts b/apps/web/src/utils/flags.ts index 5e7671e8b37..d6d9d0fe0be 100644 --- a/apps/web/src/utils/flags.ts +++ b/apps/web/src/utils/flags.ts @@ -1,6 +1,5 @@ export type Flags = { showTrial?: boolean; - scim?: boolean; }; export type FlagName = keyof Flags; diff --git a/bitwarden_license/bit-web/jest.config.js b/bitwarden_license/bit-web/jest.config.js new file mode 100644 index 00000000000..84c6866a7db --- /dev/null +++ b/bitwarden_license/bit-web/jest.config.js @@ -0,0 +1,15 @@ +const { pathsToModuleNameMapper } = require("ts-jest"); + +const { compilerOptions } = require("./tsconfig"); + +const sharedConfig = require("../../libs/shared/jest.config.base"); + +module.exports = { + ...sharedConfig, + preset: "jest-preset-angular", + setupFilesAfterEnv: ["../../apps/web/test.setup.ts"], + moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { + prefix: "/", + }), + modulePathIgnorePatterns: ["jslib"], +}; diff --git a/bitwarden_license/bit-web/src/app/organizations/organizations-routing.module.ts b/bitwarden_license/bit-web/src/app/organizations/organizations-routing.module.ts index 1bfd51b7d30..525ad0f18d5 100644 --- a/bitwarden_license/bit-web/src/app/organizations/organizations-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/organizations/organizations-routing.module.ts @@ -2,12 +2,12 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "@bitwarden/angular/guards/auth.guard"; -import { Permissions } from "@bitwarden/common/enums/permissions"; +import { Organization } from "@bitwarden/common/models/domain/organization"; -import { PermissionsGuard } from "src/app/organizations/guards/permissions.guard"; +import { OrganizationPermissionsGuard } from "src/app/organizations/guards/org-permissions.guard"; import { OrganizationLayoutComponent } from "src/app/organizations/layouts/organization-layout.component"; import { ManageComponent } from "src/app/organizations/manage/manage.component"; -import { NavigationPermissionsService } from "src/app/organizations/services/navigation-permissions.service"; +import { canAccessManageTab } from "src/app/organizations/navigation-permissions"; import { ScimComponent } from "./manage/scim.component"; import { SsoComponent } from "./manage/sso.component"; @@ -16,30 +16,30 @@ const routes: Routes = [ { path: "organizations/:organizationId", component: OrganizationLayoutComponent, - canActivate: [AuthGuard, PermissionsGuard], + canActivate: [AuthGuard, OrganizationPermissionsGuard], children: [ { path: "manage", component: ManageComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { - permissions: NavigationPermissionsService.getPermissions("manage"), + organizationPermissions: canAccessManageTab, }, children: [ { path: "sso", component: SsoComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { - permissions: [Permissions.ManageSso], + organizationPermissions: (org: Organization) => org.canManageSso, }, }, { path: "scim", component: ScimComponent, - canActivate: [PermissionsGuard], + canActivate: [OrganizationPermissionsGuard], data: { - permissions: [Permissions.ManageScim], + organizationPermissions: (org: Organization) => org.canManageScim, }, }, ], diff --git a/bitwarden_license/bit-web/src/app/providers/guards/provider-permissions.guard.spec.ts b/bitwarden_license/bit-web/src/app/providers/guards/provider-permissions.guard.spec.ts new file mode 100644 index 00000000000..8c2b8e5a5bd --- /dev/null +++ b/bitwarden_license/bit-web/src/app/providers/guards/provider-permissions.guard.spec.ts @@ -0,0 +1,124 @@ +import { ActivatedRouteSnapshot, Router } from "@angular/router"; +import { mock, MockProxy } from "jest-mock-extended"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { ProviderService } from "@bitwarden/common/abstractions/provider.service"; +import { ProviderUserType } from "@bitwarden/common/enums/providerUserType"; +import { Provider } from "@bitwarden/common/models/domain/provider"; + +import { ProviderPermissionsGuard } from "./provider-permissions.guard"; + +const providerFactory = (props: Partial = {}) => + Object.assign( + new Provider(), + { + id: "myProviderId", + enabled: true, + type: ProviderUserType.ServiceUser, + }, + props + ); + +describe("Provider Permissions Guard", () => { + let router: MockProxy; + let providerService: MockProxy; + let route: MockProxy; + + let providerPermissionsGuard: ProviderPermissionsGuard; + + beforeEach(() => { + router = mock(); + providerService = mock(); + route = mock({ + params: { + providerId: providerFactory().id, + }, + data: { + providerPermissions: null, + }, + }); + + providerPermissionsGuard = new ProviderPermissionsGuard( + providerService, + router, + mock(), + mock() + ); + }); + + it("blocks navigation if provider does not exist", async () => { + providerService.get.mockResolvedValue(null); + + const actual = await providerPermissionsGuard.canActivate(route); + + expect(actual).not.toBe(true); + }); + + it("permits navigation if no permissions are specified", async () => { + const provider = providerFactory(); + providerService.get.calledWith(provider.id).mockResolvedValue(provider); + + const actual = await providerPermissionsGuard.canActivate(route); + + expect(actual).toBe(true); + }); + + it("permits navigation if the user has permissions", async () => { + const permissionsCallback = jest.fn(); + permissionsCallback.mockImplementation((provider) => true); + route.data = { + providerPermissions: permissionsCallback, + }; + + const provider = providerFactory(); + providerService.get.calledWith(provider.id).mockResolvedValue(provider); + + const actual = await providerPermissionsGuard.canActivate(route); + + expect(permissionsCallback).toHaveBeenCalled(); + expect(actual).toBe(true); + }); + + it("blocks navigation if the user does not have permissions", async () => { + const permissionsCallback = jest.fn(); + permissionsCallback.mockImplementation((org) => false); + route.data = { + providerPermissions: permissionsCallback, + }; + + const provider = providerFactory(); + providerService.get.calledWith(provider.id).mockResolvedValue(provider); + + const actual = await providerPermissionsGuard.canActivate(route); + + expect(permissionsCallback).toHaveBeenCalled(); + expect(actual).not.toBe(true); + }); + + describe("given a disabled organization", () => { + it("blocks navigation if user is not an admin", async () => { + const org = providerFactory({ + type: ProviderUserType.ServiceUser, + enabled: false, + }); + providerService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await providerPermissionsGuard.canActivate(route); + + expect(actual).not.toBe(true); + }); + + it("permits navigation if user is an admin", async () => { + const org = providerFactory({ + type: ProviderUserType.ProviderAdmin, + enabled: false, + }); + providerService.get.calledWith(org.id).mockResolvedValue(org); + + const actual = await providerPermissionsGuard.canActivate(route); + + expect(actual).toBe(true); + }); + }); +}); diff --git a/bitwarden_license/bit-web/src/app/providers/guards/provider.guard.ts b/bitwarden_license/bit-web/src/app/providers/guards/provider-permissions.guard.ts similarity index 54% rename from bitwarden_license/bit-web/src/app/providers/guards/provider.guard.ts rename to bitwarden_license/bit-web/src/app/providers/guards/provider-permissions.guard.ts index ece28a9cb04..01c6bb3c62f 100644 --- a/bitwarden_license/bit-web/src/app/providers/guards/provider.guard.ts +++ b/bitwarden_license/bit-web/src/app/providers/guards/provider-permissions.guard.ts @@ -4,26 +4,34 @@ import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { ProviderService } from "@bitwarden/common/abstractions/provider.service"; +import { Provider } from "@bitwarden/common/models/domain/provider"; @Injectable() -export class ProviderGuard implements CanActivate { +export class ProviderPermissionsGuard implements CanActivate { constructor( + private providerService: ProviderService, private router: Router, private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService, - private providerService: ProviderService + private i18nService: I18nService ) {} async canActivate(route: ActivatedRouteSnapshot) { const provider = await this.providerService.get(route.params.providerId); if (provider == null) { - this.router.navigate(["/"]); - return false; + return this.router.createUrlTree(["/"]); } + if (!provider.isProviderAdmin && !provider.enabled) { this.platformUtilsService.showToast("error", null, this.i18nService.t("providerIsDisabled")); - this.router.navigate(["/"]); - return false; + return this.router.createUrlTree(["/"]); + } + + const permissionsCallback: (provider: Provider) => boolean = route.data?.providerPermissions; + const hasSpecifiedPermissions = permissionsCallback == null || permissionsCallback(provider); + + if (!hasSpecifiedPermissions) { + this.platformUtilsService.showToast("error", null, this.i18nService.t("accessDenied")); + return this.router.createUrlTree(["/providers", provider.id]); } return true; diff --git a/bitwarden_license/bit-web/src/app/providers/guards/provider-type.guard.ts b/bitwarden_license/bit-web/src/app/providers/guards/provider-type.guard.ts deleted file mode 100644 index 730573b9468..00000000000 --- a/bitwarden_license/bit-web/src/app/providers/guards/provider-type.guard.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Injectable } from "@angular/core"; -import { ActivatedRouteSnapshot, CanActivate, Router } from "@angular/router"; - -import { ProviderService } from "@bitwarden/common/abstractions/provider.service"; -import { Permissions } from "@bitwarden/common/enums/permissions"; - -@Injectable() -export class PermissionsGuard implements CanActivate { - constructor(private providerService: ProviderService, private router: Router) {} - - async canActivate(route: ActivatedRouteSnapshot) { - const provider = await this.providerService.get(route.params.providerId); - const permissions = route.data == null ? null : (route.data.permissions as Permissions[]); - - if ( - (permissions.indexOf(Permissions.AccessEventLogs) !== -1 && provider.canAccessEventLogs) || - (permissions.indexOf(Permissions.ManageProvider) !== -1 && provider.isProviderAdmin) || - (permissions.indexOf(Permissions.ManageUsers) !== -1 && provider.canManageUsers) - ) { - return true; - } - - this.router.navigate(["/providers", provider.id]); - return false; - } -} diff --git a/bitwarden_license/bit-web/src/app/providers/providers-routing.module.ts b/bitwarden_license/bit-web/src/app/providers/providers-routing.module.ts index c521d9b5370..6090ed8c709 100644 --- a/bitwarden_license/bit-web/src/app/providers/providers-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/providers/providers-routing.module.ts @@ -2,15 +2,14 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { AuthGuard } from "@bitwarden/angular/guards/auth.guard"; -import { Permissions } from "@bitwarden/common/enums/permissions"; +import { Provider } from "@bitwarden/common/models/domain/provider"; import { FrontendLayoutComponent } from "src/app/layouts/frontend-layout.component"; import { ProvidersComponent } from "src/app/providers/providers.component"; import { ClientsComponent } from "./clients/clients.component"; import { CreateOrganizationComponent } from "./clients/create-organization.component"; -import { PermissionsGuard } from "./guards/provider-type.guard"; -import { ProviderGuard } from "./guards/provider.guard"; +import { ProviderPermissionsGuard } from "./guards/provider-permissions.guard"; import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { EventsComponent } from "./manage/events.component"; import { ManageComponent } from "./manage/manage.component"; @@ -54,7 +53,7 @@ const routes: Routes = [ { path: ":providerId", component: ProvidersLayoutComponent, - canActivate: [ProviderGuard], + canActivate: [ProviderPermissionsGuard], children: [ { path: "", pathMatch: "full", redirectTo: "clients" }, { path: "clients/create", component: CreateOrganizationComponent }, @@ -71,19 +70,19 @@ const routes: Routes = [ { path: "people", component: PeopleComponent, - canActivate: [PermissionsGuard], + canActivate: [ProviderPermissionsGuard], data: { titleId: "people", - permissions: [Permissions.ManageUsers], + providerPermissions: (provider: Provider) => provider.canManageUsers, }, }, { path: "events", component: EventsComponent, - canActivate: [PermissionsGuard], + canActivate: [ProviderPermissionsGuard], data: { titleId: "eventLogs", - permissions: [Permissions.AccessEventLogs], + providerPermissions: (provider: Provider) => provider.canAccessEventLogs, }, }, ], @@ -100,10 +99,10 @@ const routes: Routes = [ { path: "account", component: AccountComponent, - canActivate: [PermissionsGuard], + canActivate: [ProviderPermissionsGuard], data: { titleId: "myProvider", - permissions: [Permissions.ManageProvider], + providerPermissions: (provider: Provider) => provider.isProviderAdmin, }, }, ], diff --git a/bitwarden_license/bit-web/src/app/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/providers/providers.module.ts index d7dc71300e2..7cc3210a71a 100644 --- a/bitwarden_license/bit-web/src/app/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/providers/providers.module.ts @@ -10,8 +10,7 @@ import { OssModule } from "src/app/oss.module"; import { AddOrganizationComponent } from "./clients/add-organization.component"; import { ClientsComponent } from "./clients/clients.component"; import { CreateOrganizationComponent } from "./clients/create-organization.component"; -import { PermissionsGuard } from "./guards/provider-type.guard"; -import { ProviderGuard } from "./guards/provider.guard"; +import { ProviderPermissionsGuard } from "./guards/provider-permissions.guard"; import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { BulkConfirmComponent } from "./manage/bulk/bulk-confirm.component"; import { BulkRemoveComponent } from "./manage/bulk/bulk-remove.component"; @@ -46,7 +45,7 @@ import { SetupComponent } from "./setup/setup.component"; SetupProviderComponent, UserAddEditComponent, ], - providers: [WebProviderService, ProviderGuard, PermissionsGuard], + providers: [WebProviderService, ProviderPermissionsGuard], }) export class ProvidersModule { constructor(modalService: ModalService, componentFactoryResolver: ComponentFactoryResolver) { diff --git a/bitwarden_license/bit-web/tsconfig.spec.json b/bitwarden_license/bit-web/tsconfig.spec.json new file mode 100644 index 00000000000..6ac7373f389 --- /dev/null +++ b/bitwarden_license/bit-web/tsconfig.spec.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "files": ["../../apps/web/test.setup.ts"] +} diff --git a/jest.config.js b/jest.config.js index 3ca7b5d6e9b..e8f4ce151d4 100644 --- a/jest.config.js +++ b/jest.config.js @@ -13,6 +13,7 @@ module.exports = { "/apps/browser/jest.config.js", "/apps/cli/jest.config.js", "/apps/web/jest.config.js", + "/bitwarden_license/bit-web/jest.config.js", "/libs/angular/jest.config.js", "/libs/common/jest.config.js", diff --git a/libs/common/src/enums/permissions.ts b/libs/common/src/enums/permissions.ts deleted file mode 100644 index 46ee9066203..00000000000 --- a/libs/common/src/enums/permissions.ts +++ /dev/null @@ -1,29 +0,0 @@ -export enum Permissions { - AccessEventLogs, - AccessImportExport, - AccessReports, - /** - * @deprecated Sep 29 2021: This permission has been split out to `createNewCollections`, `editAnyCollection`, and - * `deleteAnyCollection`. It exists here for backwards compatibility with Server versions <= 1.43.0 - */ - ManageAllCollections, - /** - * @deprecated Sep 29 2021: This permission has been split out to `editAssignedCollections` and - * `deleteAssignedCollections`. It exists here for backwards compatibility with Server versions <= 1.43.0 - */ - ManageAssignedCollections, - ManageGroups, - ManageOrganization, - ManagePolicies, - ManageProvider, - ManageUsers, - ManageUsersPassword, - CreateNewCollections, - EditAnyCollection, - DeleteAnyCollection, - EditAssignedCollections, - DeleteAssignedCollections, - ManageSso, - ManageBilling, - ManageScim, -} diff --git a/libs/common/src/models/domain/organization.ts b/libs/common/src/models/domain/organization.ts index bfee2584f2a..2e552507310 100644 --- a/libs/common/src/models/domain/organization.ts +++ b/libs/common/src/models/domain/organization.ts @@ -1,6 +1,5 @@ import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType"; import { OrganizationUserType } from "../../enums/organizationUserType"; -import { Permissions } from "../../enums/permissions"; import { ProductType } from "../../enums/productType"; import { PermissionsApi } from "../api/permissionsApi"; import { OrganizationData } from "../data/organizationData"; @@ -114,7 +113,7 @@ export class Organization { } get canAccessEventLogs() { - return this.isAdmin || this.permissions.accessEventLogs; + return (this.isAdmin || this.permissions.accessEventLogs) && this.useEvents; } get canAccessImportExport() { @@ -168,11 +167,11 @@ export class Organization { } get canManageGroups() { - return this.isAdmin || this.permissions.manageGroups; + return (this.isAdmin || this.permissions.manageGroups) && this.useGroups; } get canManageSso() { - return this.isAdmin || this.permissions.manageSso; + return (this.isAdmin || this.permissions.manageSso) && this.useSso; } get canManageScim() { @@ -180,7 +179,7 @@ export class Organization { } get canManagePolicies() { - return this.isAdmin || this.permissions.managePolicies; + return (this.isAdmin || this.permissions.managePolicies) && this.usePolicies; } get canManageUsers() { @@ -195,30 +194,6 @@ export class Organization { return this.canManagePolicies; } - hasAnyPermission(permissions: Permissions[]) { - const specifiedPermissions = - (permissions.includes(Permissions.AccessEventLogs) && this.canAccessEventLogs) || - (permissions.includes(Permissions.AccessImportExport) && this.canAccessImportExport) || - (permissions.includes(Permissions.AccessReports) && this.canAccessReports) || - (permissions.includes(Permissions.CreateNewCollections) && this.canCreateNewCollections) || - (permissions.includes(Permissions.EditAnyCollection) && this.canEditAnyCollection) || - (permissions.includes(Permissions.DeleteAnyCollection) && this.canDeleteAnyCollection) || - (permissions.includes(Permissions.EditAssignedCollections) && - this.canEditAssignedCollections) || - (permissions.includes(Permissions.DeleteAssignedCollections) && - this.canDeleteAssignedCollections) || - (permissions.includes(Permissions.ManageGroups) && this.canManageGroups) || - (permissions.includes(Permissions.ManageOrganization) && this.isOwner) || - (permissions.includes(Permissions.ManagePolicies) && this.canManagePolicies) || - (permissions.includes(Permissions.ManageUsers) && this.canManageUsers) || - (permissions.includes(Permissions.ManageUsersPassword) && this.canManageUsersPassword) || - (permissions.includes(Permissions.ManageSso) && this.canManageSso) || - (permissions.includes(Permissions.ManageScim) && this.canManageScim) || - (permissions.includes(Permissions.ManageBilling) && this.canManageBilling); - - return specifiedPermissions && (this.enabled || this.isOwner); - } - get canManageBilling() { return this.isOwner && (this.isProviderUser || !this.hasProvider); } diff --git a/package-lock.json b/package-lock.json index 47061648a11..67195454887 100644 --- a/package-lock.json +++ b/package-lock.json @@ -143,7 +143,7 @@ "husky": "^8.0.1", "jasmine-core": "^3.7.1", "jasmine-spec-reporter": "^7.0.0", - "jest-mock-extended": "^2.0.6", + "jest-mock-extended": "2.0.6", "jest-preset-angular": "^12.1.0", "lint-staged": "^13.0.3", "mini-css-extract-plugin": "^2.4.5", @@ -28286,9 +28286,9 @@ } }, "node_modules/jest-mock-extended": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.7.tgz", - "integrity": "sha512-h8brJJN5BZb03hTwplvt+raT6Nj0U2U71Z26Py12Qc3kvYnAjDW/zSuQJLnXCNyyufy592VC9k3X7AOz+2H52g==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.6.tgz", + "integrity": "sha512-KoDdjqwIp2phaOWB0hr4O+9HF7hIJx7O+Reefi3iGrNhUpzVkod9UozYTSanvbNvjFYIEH6noA2tIjc8IDpadw==", "dev": true, "dependencies": { "ts-essentials": "^7.0.3" @@ -64464,9 +64464,9 @@ } }, "jest-mock-extended": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.7.tgz", - "integrity": "sha512-h8brJJN5BZb03hTwplvt+raT6Nj0U2U71Z26Py12Qc3kvYnAjDW/zSuQJLnXCNyyufy592VC9k3X7AOz+2H52g==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.6.tgz", + "integrity": "sha512-KoDdjqwIp2phaOWB0hr4O+9HF7hIJx7O+Reefi3iGrNhUpzVkod9UozYTSanvbNvjFYIEH6noA2tIjc8IDpadw==", "dev": true, "requires": { "ts-essentials": "^7.0.3" diff --git a/package.json b/package.json index 31c558d2d69..4a3ed190cde 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "husky": "^8.0.1", "jasmine-core": "^3.7.1", "jasmine-spec-reporter": "^7.0.0", - "jest-mock-extended": "^2.0.6", + "jest-mock-extended": "2.0.6", "jest-preset-angular": "^12.1.0", "lint-staged": "^13.0.3", "mini-css-extract-plugin": "^2.4.5", From 4b70278b3f652ff5100821175ec1d322075acb0d Mon Sep 17 00:00:00 2001 From: Daniel James Smith Date: Mon, 15 Aug 2022 20:38:01 +0200 Subject: [PATCH 10/16] Do not call load twice on init of LockComponent (#3295) --- libs/angular/src/components/lock.component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/angular/src/components/lock.component.ts b/libs/angular/src/components/lock.component.ts index 83d5744dfa7..25759e9f29a 100644 --- a/libs/angular/src/components/lock.component.ts +++ b/libs/angular/src/components/lock.component.ts @@ -58,8 +58,6 @@ export class LockComponent implements OnInit, OnDestroy { ) {} async ngOnInit() { - // Load the first and observe updates - await this.load(); this.activeAccountSubscription = this.stateService.activeAccount$.subscribe(async () => { await this.load(); }); From ce4fd26e0d9622b9c0340e629bc7d20e115806cb Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Mon, 15 Aug 2022 16:21:08 -0400 Subject: [PATCH 11/16] [SG-587] Enabled showTrial feature flag in cloud (#3311) * Enabled showTrial feature flag in cloud and selfhosted. --- apps/web/config/cloud.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/config/cloud.json b/apps/web/config/cloud.json index 088b52f6dc9..96d692f7e8d 100644 --- a/apps/web/config/cloud.json +++ b/apps/web/config/cloud.json @@ -16,6 +16,6 @@ "proxyEvents": "https://events.bitwarden.com" }, "flags": { - "showTrial": false + "showTrial": true } } From 9d0dd613fb059e4e5b3328a0c10a9e167859a756 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Tue, 16 Aug 2022 05:59:28 -0600 Subject: [PATCH 12/16] PS-1267 Null check URI values (#3315) --- libs/common/src/services/search.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/services/search.service.ts b/libs/common/src/services/search.service.ts index 919a58941ee..fe25698b70c 100644 --- a/libs/common/src/services/search.service.ts +++ b/libs/common/src/services/search.service.ts @@ -196,7 +196,7 @@ export class SearchService implements SearchServiceAbstraction { if ( c.login && c.login.hasUris && - c.login.uris.some((loginUri) => loginUri.uri.toLowerCase().indexOf(query) > -1) + c.login.uris.some((loginUri) => loginUri?.uri?.toLowerCase().indexOf(query) > -1) ) { return true; } From 53393446303b284ac8052e0a1461ee2f7adeb0bd Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Tue, 16 Aug 2022 06:05:03 -0600 Subject: [PATCH 13/16] PS-1133 Feature/mv3 browser observable memory caching (#3245) * Create sessions sync structure * Add observing to session-syncer * Do not run syncer logic in decorator tests * Extract test constants * Change Observables to BehaviorSubject * Move sendMessage to static method in BrowserApi * Implement session sync * only watch in manifest v3 * Use session sync on folder service * Add array observable sync * Bypass cache on update from message * Create feature and dev flags for browser * Protect development-only methods with decorator * Improve todo comments for long-term residency * Use class properties in init * Do not reuse mocks * Use json (de)serialization patterns * Fix failing session storage in dev environment * Split up complex EncString constructor * Default false for decrypted session storage * Try removing hydrate EncString method * PR review * PR test review --- .eslintignore | 3 + apps/browser/config/base.json | 4 + apps/browser/config/config.js | 30 +++ apps/browser/config/development.json | 6 + apps/browser/config/production.json | 3 + .../browser/src/background/main.background.ts | 2 +- apps/browser/src/browser/browserApi.ts | 5 + .../src/decorators/dev-flag.decorator.spec.ts | 35 ++++ .../src/decorators/dev-flag.decorator.ts | 15 ++ .../browser-session.decorator.spec.ts | 64 +++++++ .../browser-session.decorator.ts | 47 +++++ .../session-sync-observable/index.ts | 2 + .../session-storable.ts | 7 + .../session-sync.decorator.spec.ts | 23 +++ .../session-sync.decorator.ts | 51 ++++++ .../session-syncer.spec.ts | 156 ++++++++++++++++ .../session-sync-observable/session-syncer.ts | 79 ++++++++ .../sync-item-metadata.ts | 22 +++ .../synced-item-metadata.spec.ts | 54 ++++++ apps/browser/src/flags.ts | 41 +++++ .../services/abstractions/state.service.ts | 2 + .../src/services/browserMessaging.service.ts | 5 +- .../src/services/folders/folder.service.ts | 15 ++ .../localBackedSessionStorage.service.ts | 52 ++++-- .../src/services/state.service.spec.ts | 140 +++++++++----- apps/browser/src/services/state.service.ts | 11 ++ apps/browser/test.setup.ts | 50 ++--- apps/browser/webpack.config.js | 7 + apps/cli/config/config.js | 1 - .../spec/models/domain/encString.spec.ts | 8 + libs/common/spec/models/domain/folder.spec.ts | 24 +++ .../spec/models/view/folderView.spec.ts | 22 +++ .../src/abstractions/storage.service.ts | 4 + libs/common/src/models/domain/encString.ts | 173 ++++++++++-------- libs/common/src/models/domain/folder.ts | 7 + libs/common/src/models/view/folderView.ts | 7 + .../src/services/folder/folder.service.ts | 4 +- 37 files changed, 1018 insertions(+), 163 deletions(-) create mode 100644 apps/browser/config/base.json create mode 100644 apps/browser/config/config.js create mode 100644 apps/browser/config/development.json create mode 100644 apps/browser/config/production.json create mode 100644 apps/browser/src/decorators/dev-flag.decorator.spec.ts create mode 100644 apps/browser/src/decorators/dev-flag.decorator.ts create mode 100644 apps/browser/src/decorators/session-sync-observable/browser-session.decorator.spec.ts create mode 100644 apps/browser/src/decorators/session-sync-observable/browser-session.decorator.ts create mode 100644 apps/browser/src/decorators/session-sync-observable/index.ts create mode 100644 apps/browser/src/decorators/session-sync-observable/session-storable.ts create mode 100644 apps/browser/src/decorators/session-sync-observable/session-sync.decorator.spec.ts create mode 100644 apps/browser/src/decorators/session-sync-observable/session-sync.decorator.ts create mode 100644 apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts create mode 100644 apps/browser/src/decorators/session-sync-observable/session-syncer.ts create mode 100644 apps/browser/src/decorators/session-sync-observable/sync-item-metadata.ts create mode 100644 apps/browser/src/decorators/session-sync-observable/synced-item-metadata.spec.ts create mode 100644 apps/browser/src/flags.ts create mode 100644 apps/browser/src/services/folders/folder.service.ts create mode 100644 libs/common/spec/models/view/folderView.spec.ts diff --git a/.eslintignore b/.eslintignore index 4e2b3a58fd6..2dff9d9aebc 100644 --- a/.eslintignore +++ b/.eslintignore @@ -8,6 +8,7 @@ **/jest.config.js **/gulpfile.js +apps/browser/config/config.js apps/browser/src/content/autofill.js apps/browser/src/scripts/duo.js @@ -18,3 +19,5 @@ apps/web/config.js apps/web/scripts/*.js apps/web/src/theme.js apps/web/tailwind.config.js + +apps/cli/config/config.js diff --git a/apps/browser/config/base.json b/apps/browser/config/base.json new file mode 100644 index 00000000000..6df6c2cfdb1 --- /dev/null +++ b/apps/browser/config/base.json @@ -0,0 +1,4 @@ +{ + "dev_flags": {}, + "flags": {} +} diff --git a/apps/browser/config/config.js b/apps/browser/config/config.js new file mode 100644 index 00000000000..81e2d619fee --- /dev/null +++ b/apps/browser/config/config.js @@ -0,0 +1,30 @@ +function load(envName) { + return { + ...loadConfig(envName), + ...loadConfig("local"), + }; +} + +function log(configObj) { + const repeatNum = 50; + console.log(`${"=".repeat(repeatNum)}\nenvConfig`); + console.log(JSON.stringify(configObj, null, 2)); + console.log(`${"=".repeat(repeatNum)}`); +} + +function loadConfig(configName) { + try { + return require(`./${configName}.json`); + } catch (e) { + if (e instanceof Error && e.code === "MODULE_NOT_FOUND") { + return {}; + } else { + throw e; + } + } +} + +module.exports = { + load, + log, +}; diff --git a/apps/browser/config/development.json b/apps/browser/config/development.json new file mode 100644 index 00000000000..a91e00ae5c4 --- /dev/null +++ b/apps/browser/config/development.json @@ -0,0 +1,6 @@ +{ + "devFlags": { + "storeSessionDecrypted": false + }, + "flags": {} +} diff --git a/apps/browser/config/production.json b/apps/browser/config/production.json new file mode 100644 index 00000000000..b04d1531a2f --- /dev/null +++ b/apps/browser/config/production.json @@ -0,0 +1,3 @@ +{ + "flags": {} +} diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 3b0f92e8218..04f963e1861 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -54,7 +54,6 @@ import { EventService } from "@bitwarden/common/services/event.service"; import { ExportService } from "@bitwarden/common/services/export.service"; import { FileUploadService } from "@bitwarden/common/services/fileUpload.service"; import { FolderApiService } from "@bitwarden/common/services/folder/folder-api.service"; -import { FolderService } from "@bitwarden/common/services/folder/folder.service"; import { KeyConnectorService } from "@bitwarden/common/services/keyConnector.service"; import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service"; import { NotificationsService } from "@bitwarden/common/services/notifications.service"; @@ -90,6 +89,7 @@ import BrowserLocalStorageService from "../services/browserLocalStorage.service" import BrowserMessagingService from "../services/browserMessaging.service"; import BrowserMessagingPrivateModeBackgroundService from "../services/browserMessagingPrivateModeBackground.service"; import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service"; +import { FolderService } from "../services/folders/folder.service"; import I18nService from "../services/i18n.service"; import { KeyGenerationService } from "../services/keyGeneration.service"; import { LocalBackedSessionStorageService } from "../services/localBackedSessionStorage.service"; diff --git a/apps/browser/src/browser/browserApi.ts b/apps/browser/src/browser/browserApi.ts index b5991be4ce3..7b0152a9bdb 100644 --- a/apps/browser/src/browser/browserApi.ts +++ b/apps/browser/src/browser/browserApi.ts @@ -115,6 +115,11 @@ export class BrowserApi { ); } + static sendMessage(subscriber: string, arg: any = {}) { + const message = Object.assign({}, { command: subscriber }, arg); + return chrome.runtime.sendMessage(message); + } + static async closeLoginTab() { const tabs = await BrowserApi.tabsQuery({ active: true, diff --git a/apps/browser/src/decorators/dev-flag.decorator.spec.ts b/apps/browser/src/decorators/dev-flag.decorator.spec.ts new file mode 100644 index 00000000000..c5401f8a097 --- /dev/null +++ b/apps/browser/src/decorators/dev-flag.decorator.spec.ts @@ -0,0 +1,35 @@ +import { devFlagEnabled } from "../flags"; + +import { devFlag } from "./dev-flag.decorator"; + +let devFlagEnabledMock: jest.Mock; +jest.mock("../flags", () => ({ + ...jest.requireActual("../flags"), + devFlagEnabled: jest.fn(), +})); + +class TestClass { + @devFlag("storeSessionDecrypted") test() { + return "test"; + } +} + +describe("devFlag decorator", () => { + beforeEach(() => { + devFlagEnabledMock = devFlagEnabled as jest.Mock; + }); + + it("should throw an error if the dev flag is disabled", () => { + devFlagEnabledMock.mockReturnValue(false); + expect(() => { + new TestClass().test(); + }).toThrowError("This method should not be called, it is protected by a disabled dev flag."); + }); + + it("should not throw an error if the dev flag is enabled", () => { + devFlagEnabledMock.mockReturnValue(true); + expect(() => { + new TestClass().test(); + }).not.toThrowError(); + }); +}); diff --git a/apps/browser/src/decorators/dev-flag.decorator.ts b/apps/browser/src/decorators/dev-flag.decorator.ts new file mode 100644 index 00000000000..da0c9934465 --- /dev/null +++ b/apps/browser/src/decorators/dev-flag.decorator.ts @@ -0,0 +1,15 @@ +import { devFlagEnabled, DevFlagName } from "../flags"; + +export function devFlag(flag: DevFlagName) { + return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { + const originalMethod = descriptor.value; + descriptor.value = function (...args: any[]) { + if (!devFlagEnabled(flag)) { + throw new Error( + `This method should not be called, it is protected by a disabled dev flag.` + ); + } + return originalMethod.apply(this, args); + }; + }; +} diff --git a/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.spec.ts b/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.spec.ts new file mode 100644 index 00000000000..b48658efa39 --- /dev/null +++ b/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.spec.ts @@ -0,0 +1,64 @@ +import { BehaviorSubject } from "rxjs"; + +import { StateService } from "../../services/state.service"; + +import { browserSession } from "./browser-session.decorator"; +import { SessionStorable } from "./session-storable"; +import { sessionSync } from "./session-sync.decorator"; + +// browserSession initializes SessionSyncers for each sessionSync decorated property +// We don't want to test SessionSyncers, so we'll mock them +jest.mock("./session-syncer"); + +describe("browserSession decorator", () => { + it("should throw if StateService is not a constructor argument", () => { + @browserSession + class TestClass {} + expect(() => { + new TestClass(); + }).toThrowError( + "Cannot decorate TestClass with browserSession, Browser's StateService must be injected" + ); + }); + + it("should create if StateService is a constructor argument", () => { + const stateService = Object.create(StateService.prototype, {}); + + @browserSession + class TestClass { + constructor(private stateService: StateService) {} + } + + expect(new TestClass(stateService)).toBeDefined(); + }); + + describe("interaction with @sessionSync decorator", () => { + let stateService: StateService; + + @browserSession + class TestClass { + @sessionSync({ initializer: (s: string) => s }) + behaviorSubject = new BehaviorSubject(""); + + constructor(private stateService: StateService) {} + + fromJSON(json: any) { + this.behaviorSubject.next(json); + } + } + + beforeEach(() => { + stateService = Object.create(StateService.prototype, {}) as StateService; + }); + + it("should create a session syncer", () => { + const testClass = new TestClass(stateService) as any as SessionStorable; + expect(testClass.__sessionSyncers.length).toEqual(1); + }); + + it("should initialize the session syncer", () => { + const testClass = new TestClass(stateService) as any as SessionStorable; + expect(testClass.__sessionSyncers[0].init).toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.ts b/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.ts new file mode 100644 index 00000000000..bee93173d19 --- /dev/null +++ b/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.ts @@ -0,0 +1,47 @@ +import { Constructor } from "type-fest"; + +import { StateService } from "../../services/state.service"; + +import { SessionStorable } from "./session-storable"; +import { SessionSyncer } from "./session-syncer"; +import { SyncedItemMetadata } from "./sync-item-metadata"; + +/** + * Mark the class as syncing state across the browser session. This decorator finds rxjs BehaviorSubject properties + * marked with @sessionSync and syncs these values across the browser session. + * + * @param constructor + * @returns A new constructor that extends the original one to add session syncing. + */ +export function browserSession>(constructor: TCtor) { + return class extends constructor implements SessionStorable { + __syncedItemMetadata: SyncedItemMetadata[]; + __sessionSyncers: SessionSyncer[]; + + constructor(...args: any[]) { + super(...args); + + // Require state service to be injected + const stateService = args.find((arg) => arg instanceof StateService); + if (!stateService) { + throw new Error( + `Cannot decorate ${constructor.name} with browserSession, Browser's StateService must be injected` + ); + } + + if (this.__syncedItemMetadata == null || !(this.__syncedItemMetadata instanceof Array)) { + return; + } + + this.__sessionSyncers = this.__syncedItemMetadata.map((metadata) => + this.buildSyncer(metadata, stateService) + ); + } + + buildSyncer(metadata: SyncedItemMetadata, stateService: StateService) { + const syncer = new SessionSyncer((this as any)[metadata.key], stateService, metadata); + syncer.init(); + return syncer; + } + }; +} diff --git a/apps/browser/src/decorators/session-sync-observable/index.ts b/apps/browser/src/decorators/session-sync-observable/index.ts new file mode 100644 index 00000000000..c0c547192e7 --- /dev/null +++ b/apps/browser/src/decorators/session-sync-observable/index.ts @@ -0,0 +1,2 @@ +export { browserSession } from "./browser-session.decorator"; +export { sessionSync } from "./session-sync.decorator"; diff --git a/apps/browser/src/decorators/session-sync-observable/session-storable.ts b/apps/browser/src/decorators/session-sync-observable/session-storable.ts new file mode 100644 index 00000000000..f5838b86ef9 --- /dev/null +++ b/apps/browser/src/decorators/session-sync-observable/session-storable.ts @@ -0,0 +1,7 @@ +import { SessionSyncer } from "./session-syncer"; +import { SyncedItemMetadata } from "./sync-item-metadata"; + +export interface SessionStorable { + __syncedItemMetadata: SyncedItemMetadata[]; + __sessionSyncers: SessionSyncer[]; +} diff --git a/apps/browser/src/decorators/session-sync-observable/session-sync.decorator.spec.ts b/apps/browser/src/decorators/session-sync-observable/session-sync.decorator.spec.ts new file mode 100644 index 00000000000..d1cb8e7d155 --- /dev/null +++ b/apps/browser/src/decorators/session-sync-observable/session-sync.decorator.spec.ts @@ -0,0 +1,23 @@ +import { BehaviorSubject } from "rxjs"; + +import { sessionSync } from "./session-sync.decorator"; + +describe("sessionSync decorator", () => { + const initializer = (s: string) => "test"; + const ctor = String; + class TestClass { + @sessionSync({ ctor: ctor, initializer: initializer }) + testProperty = new BehaviorSubject(""); + } + + it("should add __syncedItemKeys to prototype", () => { + const testClass = new TestClass(); + expect((testClass as any).__syncedItemMetadata).toEqual([ + expect.objectContaining({ + key: "TestClass_testProperty", + ctor: ctor, + initializer: initializer, + }), + ]); + }); +}); diff --git a/apps/browser/src/decorators/session-sync-observable/session-sync.decorator.ts b/apps/browser/src/decorators/session-sync-observable/session-sync.decorator.ts new file mode 100644 index 00000000000..e9887c00850 --- /dev/null +++ b/apps/browser/src/decorators/session-sync-observable/session-sync.decorator.ts @@ -0,0 +1,51 @@ +import { Jsonify } from "type-fest"; + +import { SessionStorable } from "./session-storable"; + +class BuildOptions { + ctor?: new () => T; + initializer?: (keyValuePair: Jsonify) => T; + initializeAsArray? = false; +} + +/** + * A decorator used to indicate the BehaviorSubject should be synced for this browser session across all contexts. + * + * >**Note** This decorator does nothing if the enclosing class is not decorated with @browserSession. + * + * >**Note** The Behavior subject must be initialized with a default or in the constructor of the class. If it is not, an error will be thrown. + * + * >**!!Warning!!** If the property is overwritten at any time, the new value will not be synced across the browser session. + * + * @param buildOptions + * Builders for the value, requires either a constructor (ctor) for your BehaviorSubject type or an + * initializer function that takes a key value pair representation of the BehaviorSubject data + * and returns your instantiated BehaviorSubject value. `initializeAsArray can optionally be used to indicate + * the provided initializer function should be used to build an array of values. For example, + * ```ts + * \@sessionSync({ initializer: Foo.fromJSON, initializeAsArray: true }) + * ``` + * is equivalent to + * ``` + * \@sessionSync({ initializer: (obj: any[]) => obj.map((f) => Foo.fromJSON }) + * ``` + * + * @returns decorator function + */ +export function sessionSync(buildOptions: BuildOptions) { + return (prototype: unknown, propertyKey: string) => { + // Force prototype into SessionStorable and implement it. + const p = prototype as SessionStorable; + + if (p.__syncedItemMetadata == null) { + p.__syncedItemMetadata = []; + } + + p.__syncedItemMetadata.push({ + key: `${prototype.constructor.name}_${propertyKey}`, + ctor: buildOptions.ctor, + initializer: buildOptions.initializer, + initializeAsArray: buildOptions.initializeAsArray, + }); + }; +} diff --git a/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts b/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts new file mode 100644 index 00000000000..b08ee85647d --- /dev/null +++ b/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts @@ -0,0 +1,156 @@ +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { BrowserApi } from "../../browser/browserApi"; +import { StateService } from "../../services/abstractions/state.service"; + +import { SessionSyncer } from "./session-syncer"; + +describe("session syncer", () => { + const key = "Test__behaviorSubject"; + const metaData = { key, initializer: (s: string) => s }; + let stateService: MockProxy; + let sut: SessionSyncer; + let behaviorSubject: BehaviorSubject; + + beforeEach(() => { + behaviorSubject = new BehaviorSubject(""); + jest.spyOn(chrome.runtime, "getManifest").mockReturnValue({ + name: "bitwarden-test", + version: "0.0.0", + manifest_version: 3, + }); + + stateService = mock(); + sut = new SessionSyncer(behaviorSubject, stateService, metaData); + }); + + afterEach(() => { + jest.resetAllMocks(); + + behaviorSubject.unsubscribe(); + }); + + describe("constructor", () => { + it("should throw if behaviorSubject is not an instance of BehaviorSubject", () => { + expect(() => { + new SessionSyncer({} as any, stateService, null); + }).toThrowError("behaviorSubject must be an instance of BehaviorSubject"); + }); + + it("should create if either ctor or initializer is provided", () => { + expect( + new SessionSyncer(behaviorSubject, stateService, { key: key, ctor: String }) + ).toBeDefined(); + expect( + new SessionSyncer(behaviorSubject, stateService, { + key: key, + initializer: (s: any) => s, + }) + ).toBeDefined(); + }); + it("should throw if neither ctor or initializer is provided", () => { + expect(() => { + new SessionSyncer(behaviorSubject, stateService, { key: key }); + }).toThrowError("ctor or initializer must be provided"); + }); + }); + + describe("manifest v2 init", () => { + let observeSpy: jest.SpyInstance; + let listenForUpdatesSpy: jest.SpyInstance; + + beforeEach(() => { + observeSpy = jest.spyOn(behaviorSubject, "subscribe").mockReturnThis(); + listenForUpdatesSpy = jest.spyOn(BrowserApi, "messageListener").mockReturnValue(); + jest.spyOn(chrome.runtime, "getManifest").mockReturnValue({ + name: "bitwarden-test", + version: "0.0.0", + manifest_version: 2, + }); + + sut.init(); + }); + + it("should not start observing", () => { + expect(observeSpy).not.toHaveBeenCalled(); + }); + + it("should not start listening", () => { + expect(listenForUpdatesSpy).not.toHaveBeenCalled(); + }); + }); + + describe("a value is emitted on the observable", () => { + let sendMessageSpy: jest.SpyInstance; + + beforeEach(() => { + sendMessageSpy = jest.spyOn(BrowserApi, "sendMessage"); + + sut.init(); + + behaviorSubject.next("test"); + }); + + it("should update the session memory", async () => { + // await finishing of fire-and-forget operation + await new Promise((resolve) => setTimeout(resolve, 100)); + expect(stateService.setInSessionMemory).toHaveBeenCalledTimes(1); + expect(stateService.setInSessionMemory).toHaveBeenCalledWith(key, "test"); + }); + + it("should update sessionSyncers in other contexts", async () => { + // await finishing of fire-and-forget operation + await new Promise((resolve) => setTimeout(resolve, 100)); + + expect(sendMessageSpy).toHaveBeenCalledTimes(1); + expect(sendMessageSpy).toHaveBeenCalledWith(`${key}_update`, { id: sut.id }); + }); + }); + + describe("A message is received", () => { + let nextSpy: jest.SpyInstance; + let sendMessageSpy: jest.SpyInstance; + + beforeEach(() => { + nextSpy = jest.spyOn(behaviorSubject, "next"); + sendMessageSpy = jest.spyOn(BrowserApi, "sendMessage"); + + sut.init(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it("should ignore messages with the wrong command", async () => { + await sut.updateFromMessage({ command: "wrong_command", id: sut.id }); + + expect(stateService.getFromSessionMemory).not.toHaveBeenCalled(); + expect(nextSpy).not.toHaveBeenCalled(); + }); + + it("should ignore messages from itself", async () => { + await sut.updateFromMessage({ command: `${key}_update`, id: sut.id }); + + expect(stateService.getFromSessionMemory).not.toHaveBeenCalled(); + expect(nextSpy).not.toHaveBeenCalled(); + }); + + it("should update from message on emit from another instance", async () => { + stateService.getFromSessionMemory.mockResolvedValue("test"); + + await sut.updateFromMessage({ command: `${key}_update`, id: "different_id" }); + + expect(stateService.getFromSessionMemory).toHaveBeenCalledTimes(1); + expect(stateService.getFromSessionMemory).toHaveBeenCalledWith(key); + + expect(nextSpy).toHaveBeenCalledTimes(1); + expect(nextSpy).toHaveBeenCalledWith("test"); + expect(behaviorSubject.value).toBe("test"); + + // Expect no circular messaging + expect(sendMessageSpy).toHaveBeenCalledTimes(0); + }); + }); +}); diff --git a/apps/browser/src/decorators/session-sync-observable/session-syncer.ts b/apps/browser/src/decorators/session-sync-observable/session-syncer.ts new file mode 100644 index 00000000000..0bfc56d5211 --- /dev/null +++ b/apps/browser/src/decorators/session-sync-observable/session-syncer.ts @@ -0,0 +1,79 @@ +import { BehaviorSubject, Subscription } from "rxjs"; + +import { Utils } from "@bitwarden/common/misc/utils"; + +import { BrowserApi } from "../../browser/browserApi"; +import { StateService } from "../../services/abstractions/state.service"; + +import { SyncedItemMetadata } from "./sync-item-metadata"; + +export class SessionSyncer { + subscription: Subscription; + id = Utils.newGuid(); + + // everyone gets the same initial values + private ignoreNextUpdate = true; + + constructor( + private behaviorSubject: BehaviorSubject, + private stateService: StateService, + private metaData: SyncedItemMetadata + ) { + if (!(behaviorSubject instanceof BehaviorSubject)) { + throw new Error("behaviorSubject must be an instance of BehaviorSubject"); + } + + if (metaData.ctor == null && metaData.initializer == null) { + throw new Error("ctor or initializer must be provided"); + } + } + + init() { + if (chrome.runtime.getManifest().manifest_version != 3) { + return; + } + + this.observe(); + this.listenForUpdates(); + } + + private observe() { + // This may be a memory leak. + // There is no good time to unsubscribe from this observable. Hopefully Manifest V3 clears memory from temporary + // contexts. If so, this is handled by destruction of the context. + this.subscription = this.behaviorSubject.subscribe(async (next) => { + if (this.ignoreNextUpdate) { + this.ignoreNextUpdate = false; + return; + } + await this.updateSession(next); + }); + } + + private listenForUpdates() { + // This is an unawaited promise, but it will be executed asynchronously in the background. + BrowserApi.messageListener( + this.updateMessageCommand, + async (message) => await this.updateFromMessage(message) + ); + } + + async updateFromMessage(message: any) { + if (message.command != this.updateMessageCommand || message.id === this.id) { + return; + } + const keyValuePair = await this.stateService.getFromSessionMemory(this.metaData.key); + const value = SyncedItemMetadata.buildFromKeyValuePair(keyValuePair, this.metaData); + this.ignoreNextUpdate = true; + this.behaviorSubject.next(value); + } + + private async updateSession(value: any) { + await this.stateService.setInSessionMemory(this.metaData.key, value); + await BrowserApi.sendMessage(this.updateMessageCommand, { id: this.id }); + } + + private get updateMessageCommand() { + return `${this.metaData.key}_update`; + } +} diff --git a/apps/browser/src/decorators/session-sync-observable/sync-item-metadata.ts b/apps/browser/src/decorators/session-sync-observable/sync-item-metadata.ts new file mode 100644 index 00000000000..7f632c962ea --- /dev/null +++ b/apps/browser/src/decorators/session-sync-observable/sync-item-metadata.ts @@ -0,0 +1,22 @@ +export class SyncedItemMetadata { + key: string; + ctor?: new () => any; + initializer?: (keyValuePair: any) => any; + initializeAsArray?: boolean; + + static buildFromKeyValuePair(keyValuePair: any, metadata: SyncedItemMetadata): any { + const builder = SyncedItemMetadata.getBuilder(metadata); + + if (metadata.initializeAsArray) { + return keyValuePair.map((o: any) => builder(o)); + } else { + return builder(keyValuePair); + } + } + + private static getBuilder(metadata: SyncedItemMetadata): (o: any) => any { + return metadata.initializer != null + ? metadata.initializer + : (o: any) => Object.assign(new metadata.ctor(), o); + } +} diff --git a/apps/browser/src/decorators/session-sync-observable/synced-item-metadata.spec.ts b/apps/browser/src/decorators/session-sync-observable/synced-item-metadata.spec.ts new file mode 100644 index 00000000000..c8e819dbe6e --- /dev/null +++ b/apps/browser/src/decorators/session-sync-observable/synced-item-metadata.spec.ts @@ -0,0 +1,54 @@ +import { SyncedItemMetadata } from "./sync-item-metadata"; + +describe("build from key value pair", () => { + const key = "key"; + const initializer = (s: any) => "used initializer"; + class TestClass {} + const ctor = TestClass; + + it("should call initializer if provided", () => { + const actual = SyncedItemMetadata.buildFromKeyValuePair( + {}, + { + key: "key", + initializer: initializer, + } + ); + + expect(actual).toEqual("used initializer"); + }); + + it("should call ctor if provided", () => { + const expected = { provided: "value" }; + const actual = SyncedItemMetadata.buildFromKeyValuePair(expected, { + key: key, + ctor: ctor, + }); + + expect(actual).toBeInstanceOf(ctor); + expect(actual).toEqual(expect.objectContaining(expected)); + }); + + it("should prefer using initializer if both are provided", () => { + const actual = SyncedItemMetadata.buildFromKeyValuePair( + {}, + { + key: key, + initializer: initializer, + ctor: ctor, + } + ); + + expect(actual).toEqual("used initializer"); + }); + + it("should honor initialize as array", () => { + const actual = SyncedItemMetadata.buildFromKeyValuePair([1, 2], { + key: key, + initializer: initializer, + initializeAsArray: true, + }); + + expect(actual).toEqual(["used initializer", "used initializer"]); + }); +}); diff --git a/apps/browser/src/flags.ts b/apps/browser/src/flags.ts new file mode 100644 index 00000000000..2480c2cace7 --- /dev/null +++ b/apps/browser/src/flags.ts @@ -0,0 +1,41 @@ +function getFlags(envFlags: string | T): T { + if (typeof envFlags === "string") { + return JSON.parse(envFlags) as T; + } else { + return envFlags as T; + } +} + +/* Placeholder for when we have a relevant feature flag +export type Flags = { test?: boolean }; +export type FlagName = keyof Flags; +export function flagEnabled(flag: FlagName): boolean { + const flags = getFlags(process.env.FLAGS); + return flags[flag] == null || flags[flag]; +} +*/ + +/** + * These flags are useful for development and testing. + * Dev Flags are always OFF in production. + */ +export type DevFlags = { + storeSessionDecrypted?: boolean; +}; + +export type DevFlagName = keyof DevFlags; + +/** + * Gets the value of a dev flag from environment. + * Will always return false unless in development. + * @param flag The name of the dev flag to check + * @returns The value of the flag + */ +export function devFlagEnabled(flag: DevFlagName): boolean { + if (process.env.ENV !== "development") { + return false; + } + + const devFlags = getFlags(process.env.DEV_FLAGS); + return devFlags[flag] == null || devFlags[flag]; +} diff --git a/apps/browser/src/services/abstractions/state.service.ts b/apps/browser/src/services/abstractions/state.service.ts index 1ed5ae24a23..ca2a5ced7fa 100644 --- a/apps/browser/src/services/abstractions/state.service.ts +++ b/apps/browser/src/services/abstractions/state.service.ts @@ -7,6 +7,8 @@ import { BrowserGroupingsComponentState } from "src/models/browserGroupingsCompo import { BrowserSendComponentState } from "src/models/browserSendComponentState"; export abstract class StateService extends BaseStateServiceAbstraction { + abstract getFromSessionMemory(key: string): Promise; + abstract setInSessionMemory(key: string, value: any): Promise; getBrowserGroupingComponentState: ( options?: StorageOptions ) => Promise; diff --git a/apps/browser/src/services/browserMessaging.service.ts b/apps/browser/src/services/browserMessaging.service.ts index ae121793a04..7832e08b323 100644 --- a/apps/browser/src/services/browserMessaging.service.ts +++ b/apps/browser/src/services/browserMessaging.service.ts @@ -1,8 +1,9 @@ import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; +import { BrowserApi } from "../browser/browserApi"; + export default class BrowserMessagingService implements MessagingService { send(subscriber: string, arg: any = {}) { - const message = Object.assign({}, { command: subscriber }, arg); - chrome.runtime.sendMessage(message); + return BrowserApi.sendMessage(subscriber, arg); } } diff --git a/apps/browser/src/services/folders/folder.service.ts b/apps/browser/src/services/folders/folder.service.ts new file mode 100644 index 00000000000..f97b9d0d0b2 --- /dev/null +++ b/apps/browser/src/services/folders/folder.service.ts @@ -0,0 +1,15 @@ +import { BehaviorSubject } from "rxjs/internal/BehaviorSubject"; + +import { Folder } from "@bitwarden/common/models/domain/folder"; +import { FolderView } from "@bitwarden/common/models/view/folderView"; +import { FolderService as BaseFolderService } from "@bitwarden/common/services/folder/folder.service"; + +import { browserSession, sessionSync } from "../../decorators/session-sync-observable"; + +@browserSession +export class FolderService extends BaseFolderService { + @sessionSync({ initializer: Folder.fromJSON, initializeAsArray: true }) + protected _folders: BehaviorSubject; + @sessionSync({ initializer: FolderView.fromJSON, initializeAsArray: true }) + protected _folderViews: BehaviorSubject; +} diff --git a/apps/browser/src/services/localBackedSessionStorage.service.ts b/apps/browser/src/services/localBackedSessionStorage.service.ts index 0f5305366a4..cdc7d4cf154 100644 --- a/apps/browser/src/services/localBackedSessionStorage.service.ts +++ b/apps/browser/src/services/localBackedSessionStorage.service.ts @@ -1,8 +1,11 @@ import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; -import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; +import { AbstractCachedStorageService } from "@bitwarden/common/abstractions/storage.service"; import { EncString } from "@bitwarden/common/models/domain/encString"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey"; +import { devFlag } from "../decorators/dev-flag.decorator"; +import { devFlagEnabled } from "../flags"; + import { AbstractKeyGenerationService } from "./abstractions/abstractKeyGeneration.service"; import BrowserLocalStorageService from "./browserLocalStorage.service"; import BrowserMemoryStorageService from "./browserMemoryStorage.service"; @@ -12,8 +15,8 @@ const keys = { sessionKey: "session", }; -export class LocalBackedSessionStorageService extends AbstractStorageService { - private cache = new Map(); +export class LocalBackedSessionStorageService extends AbstractCachedStorageService { + private cache = new Map(); private localStorage = new BrowserLocalStorageService(); private sessionStorage = new BrowserMemoryStorageService(); @@ -26,23 +29,27 @@ export class LocalBackedSessionStorageService extends AbstractStorageService { async get(key: string): Promise { if (this.cache.has(key)) { - return this.cache.get(key); + return this.cache.get(key) as T; } + return await this.getBypassCache(key); + } + + async getBypassCache(key: string): Promise { const session = await this.getLocalSession(await this.getSessionEncKey()); if (session == null || !Object.keys(session).includes(key)) { return null; } this.cache.set(key, session[key]); - return this.cache.get(key); + return this.cache.get(key) as T; } async has(key: string): Promise { return (await this.get(key)) != null; } - async save(key: string, obj: any): Promise { + async save(key: string, obj: T): Promise { if (obj == null) { this.cache.delete(key); } else { @@ -59,13 +66,17 @@ export class LocalBackedSessionStorageService extends AbstractStorageService { await this.save(key, null); } - async getLocalSession(encKey: SymmetricCryptoKey): Promise { + async getLocalSession(encKey: SymmetricCryptoKey): Promise> { const local = await this.localStorage.get(keys.sessionKey); if (local == null) { return null; } + if (devFlagEnabled("storeSessionDecrypted")) { + return local as any as Record; + } + const sessionJson = await this.encryptService.decryptToUtf8(new EncString(local), encKey); if (sessionJson == null) { // Error with decryption -- session is lost, delete state and key and start over @@ -76,7 +87,26 @@ export class LocalBackedSessionStorageService extends AbstractStorageService { return JSON.parse(sessionJson); } - async setLocalSession(session: any, key: SymmetricCryptoKey) { + async setLocalSession(session: Record, key: SymmetricCryptoKey) { + if (devFlagEnabled("storeSessionDecrypted")) { + await this.setDecryptedLocalSession(session); + } else { + await this.setEncryptedLocalSession(session, key); + } + } + + @devFlag("storeSessionDecrypted") + async setDecryptedLocalSession(session: Record): Promise { + // Make sure we're storing the jsonified version of the session + const jsonSession = JSON.parse(JSON.stringify(session)); + if (session == null) { + await this.localStorage.remove(keys.sessionKey); + } else { + await this.localStorage.save(keys.sessionKey, jsonSession); + } + } + + async setEncryptedLocalSession(session: Record, key: SymmetricCryptoKey) { const jsonSession = JSON.stringify(session); const encSession = await this.encryptService.encrypt(jsonSession, key); @@ -87,14 +117,12 @@ export class LocalBackedSessionStorageService extends AbstractStorageService { } async getSessionEncKey(): Promise { - let storedKey = (await this.sessionStorage.get(keys.encKey)) as SymmetricCryptoKey; + let storedKey = await this.sessionStorage.get(keys.encKey); if (storedKey == null || Object.keys(storedKey).length == 0) { storedKey = await this.keyGenerationService.makeEphemeralKey(); await this.setSessionEncKey(storedKey); } - return SymmetricCryptoKey.fromJSON( - Object.create(SymmetricCryptoKey.prototype, Object.getOwnPropertyDescriptors(storedKey)) - ); + return SymmetricCryptoKey.fromJSON(storedKey); } async setSessionEncKey(input: SymmetricCryptoKey): Promise { diff --git a/apps/browser/src/services/state.service.spec.ts b/apps/browser/src/services/state.service.spec.ts index 2fe6a578598..60813c22931 100644 --- a/apps/browser/src/services/state.service.spec.ts +++ b/apps/browser/src/services/state.service.spec.ts @@ -1,7 +1,10 @@ import { Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { LogService } from "@bitwarden/common/abstractions/log.service"; -import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; +import { + AbstractCachedStorageService, + AbstractStorageService, +} from "@bitwarden/common/abstractions/storage.service"; import { SendType } from "@bitwarden/common/enums/sendType"; import { StateFactory } from "@bitwarden/common/factories/stateFactory"; import { GlobalState } from "@bitwarden/common/models/domain/globalState"; @@ -14,12 +17,12 @@ import { BrowserComponentState } from "../models/browserComponentState"; import { BrowserGroupingsComponentState } from "../models/browserGroupingsComponentState"; import { BrowserSendComponentState } from "../models/browserSendComponentState"; +import { LocalBackedSessionStorageService } from "./localBackedSessionStorage.service"; import { StateService } from "./state.service"; describe("Browser State Service", () => { let secureStorageService: SubstituteOf; let diskStorageService: SubstituteOf; - let memoryStorageService: SubstituteOf; let logService: SubstituteOf; let stateMigrationService: SubstituteOf; let stateFactory: SubstituteOf>; @@ -33,7 +36,6 @@ describe("Browser State Service", () => { beforeEach(() => { secureStorageService = Substitute.for(); diskStorageService = Substitute.for(); - memoryStorageService = Substitute.for(); logService = Substitute.for(); stateMigrationService = Substitute.for(); stateFactory = Substitute.for(); @@ -44,66 +46,104 @@ describe("Browser State Service", () => { profile: { userId: userId }, }); state.activeUserId = userId; - const stateGetter = (key: string) => Promise.resolve(JSON.parse(JSON.stringify(state))); - memoryStorageService.get("state").mimicks(stateGetter); - - sut = new StateService( - diskStorageService, - secureStorageService, - memoryStorageService, - logService, - stateMigrationService, - stateFactory, - useAccountCache - ); }); - describe("getBrowserGroupingComponentState", () => { - it("should return a BrowserGroupingsComponentState", async () => { - state.accounts[userId].groupings = new BrowserGroupingsComponentState(); + describe("direct memory storage access", () => { + let memoryStorageService: AbstractCachedStorageService; - const actual = await sut.getBrowserGroupingComponentState(); - expect(actual).toBeInstanceOf(BrowserGroupingsComponentState); + beforeEach(() => { + // We need `AbstractCachedStorageService` in the prototype chain to correctly test cache bypass. + memoryStorageService = Object.create(LocalBackedSessionStorageService.prototype); + + sut = new StateService( + diskStorageService, + secureStorageService, + memoryStorageService, + logService, + stateMigrationService, + stateFactory, + useAccountCache + ); + }); + + it("should bypass cache if possible", async () => { + const spyBypass = jest + .spyOn(memoryStorageService, "getBypassCache") + .mockResolvedValue("value"); + const spyGet = jest.spyOn(memoryStorageService, "get"); + const result = await sut.getFromSessionMemory("key"); + expect(spyBypass).toHaveBeenCalled(); + expect(spyGet).not.toHaveBeenCalled(); + expect(result).toBe("value"); }); }); - describe("getBrowserCipherComponentState", () => { - it("should return a BrowserComponentState", async () => { - const componentState = new BrowserComponentState(); - componentState.scrollY = 0; - componentState.searchText = "test"; - state.accounts[userId].ciphers = componentState; + describe("state methods", () => { + let memoryStorageService: SubstituteOf; - const actual = await sut.getBrowserCipherComponentState(); - expect(actual).toStrictEqual(componentState); + beforeEach(() => { + memoryStorageService = Substitute.for(); + const stateGetter = (key: string) => Promise.resolve(JSON.parse(JSON.stringify(state))); + memoryStorageService.get("state").mimicks(stateGetter); + + sut = new StateService( + diskStorageService, + secureStorageService, + memoryStorageService, + logService, + stateMigrationService, + stateFactory, + useAccountCache + ); }); - }); - describe("getBrowserSendComponentState", () => { - it("should return a BrowserSendComponentState", async () => { - const sendState = new BrowserSendComponentState(); - sendState.sends = [new SendView(), new SendView()]; - sendState.typeCounts = new Map([ - [SendType.File, 3], - [SendType.Text, 5], - ]); - state.accounts[userId].send = sendState; + describe("getBrowserGroupingComponentState", () => { + it("should return a BrowserGroupingsComponentState", async () => { + state.accounts[userId].groupings = new BrowserGroupingsComponentState(); - const actual = await sut.getBrowserSendComponentState(); - expect(actual).toBeInstanceOf(BrowserSendComponentState); - expect(actual).toMatchObject(sendState); + const actual = await sut.getBrowserGroupingComponentState(); + expect(actual).toBeInstanceOf(BrowserGroupingsComponentState); + }); }); - }); - describe("getBrowserSendTypeComponentState", () => { - it("should return a BrowserComponentState", async () => { - const componentState = new BrowserComponentState(); - componentState.scrollY = 0; - componentState.searchText = "test"; - state.accounts[userId].sendType = componentState; + describe("getBrowserCipherComponentState", () => { + it("should return a BrowserComponentState", async () => { + const componentState = new BrowserComponentState(); + componentState.scrollY = 0; + componentState.searchText = "test"; + state.accounts[userId].ciphers = componentState; - const actual = await sut.getBrowserSendTypeComponentState(); - expect(actual).toStrictEqual(componentState); + const actual = await sut.getBrowserCipherComponentState(); + expect(actual).toStrictEqual(componentState); + }); + }); + + describe("getBrowserSendComponentState", () => { + it("should return a BrowserSendComponentState", async () => { + const sendState = new BrowserSendComponentState(); + sendState.sends = [new SendView(), new SendView()]; + sendState.typeCounts = new Map([ + [SendType.File, 3], + [SendType.Text, 5], + ]); + state.accounts[userId].send = sendState; + + const actual = await sut.getBrowserSendComponentState(); + expect(actual).toBeInstanceOf(BrowserSendComponentState); + expect(actual).toMatchObject(sendState); + }); + }); + + describe("getBrowserSendTypeComponentState", () => { + it("should return a BrowserComponentState", async () => { + const componentState = new BrowserComponentState(); + componentState.scrollY = 0; + componentState.searchText = "test"; + state.accounts[userId].sendType = componentState; + + const actual = await sut.getBrowserSendTypeComponentState(); + expect(actual).toStrictEqual(componentState); + }); }); }); }); diff --git a/apps/browser/src/services/state.service.ts b/apps/browser/src/services/state.service.ts index 0153be848df..6685f495e06 100644 --- a/apps/browser/src/services/state.service.ts +++ b/apps/browser/src/services/state.service.ts @@ -1,3 +1,4 @@ +import { AbstractCachedStorageService } from "@bitwarden/common/abstractions/storage.service"; import { GlobalState } from "@bitwarden/common/models/domain/globalState"; import { StorageOptions } from "@bitwarden/common/models/domain/storageOptions"; import { @@ -16,6 +17,16 @@ export class StateService extends BaseStateService implements StateServiceAbstraction { + async getFromSessionMemory(key: string): Promise { + return this.memoryStorageService instanceof AbstractCachedStorageService + ? await this.memoryStorageService.getBypassCache(key) + : await this.memoryStorageService.get(key); + } + + async setInSessionMemory(key: string, value: any): Promise { + await this.memoryStorageService.save(key, value); + } + async addAccount(account: Account) { // Apply browser overrides to default account values account = new Account(account); diff --git a/apps/browser/test.setup.ts b/apps/browser/test.setup.ts index 6215bd65f18..a231353f64b 100644 --- a/apps/browser/test.setup.ts +++ b/apps/browser/test.setup.ts @@ -1,26 +1,32 @@ // Add chrome storage api -const get = jest.fn(); -const set = jest.fn(); -const has = jest.fn(); -const remove = jest.fn(); const QUOTA_BYTES = 10; -const getBytesInUse = jest.fn(); -const clear = jest.fn(); -global.chrome = { - storage: { - local: { - set, - get, - remove, - QUOTA_BYTES, - getBytesInUse, - clear, - }, - session: { - set, - get, - has, - remove, - }, +const storage = { + local: { + set: jest.fn(), + get: jest.fn(), + remove: jest.fn(), + QUOTA_BYTES, + getBytesInUse: jest.fn(), + clear: jest.fn(), }, + session: { + set: jest.fn(), + get: jest.fn(), + has: jest.fn(), + remove: jest.fn(), + }, +}; + +const runtime = { + onMessage: { + addListener: jest.fn(), + }, + sendMessage: jest.fn(), + getManifest: jest.fn(), +}; + +// set chrome +global.chrome = { + storage, + runtime, } as any; diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index 3f16579e44b..301e20a2c26 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -6,6 +6,7 @@ const CopyWebpackPlugin = require("copy-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const { AngularWebpackPlugin } = require("@ngtools/webpack"); const TerserPlugin = require("terser-webpack-plugin"); +const configurator = require("./config/config"); if (process.env.NODE_ENV == null) { process.env.NODE_ENV = "development"; @@ -14,6 +15,8 @@ const ENV = (process.env.ENV = process.env.NODE_ENV); const manifestVersion = process.env.MANIFEST_VERSION == 3 ? 3 : 2; console.log(`Building Manifest Version ${manifestVersion} app`); +const envConfig = configurator.load(ENV); +configurator.log(envConfig); const moduleRules = [ { @@ -116,6 +119,10 @@ const plugins = [ exclude: [/content\/.*/, /notification\/.*/], filename: "[file].map", }), + new webpack.EnvironmentPlugin({ + FLAGS: envConfig.flags, + DEV_FLAGS: ENV === "development" ? envConfig.devFlags : {}, + }), ]; const config = { diff --git a/apps/cli/config/config.js b/apps/cli/config/config.js index 2b2516e7c27..81e2d619fee 100644 --- a/apps/cli/config/config.js +++ b/apps/cli/config/config.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ function load(envName) { return { ...loadConfig(envName), diff --git a/libs/common/spec/models/domain/encString.spec.ts b/libs/common/spec/models/domain/encString.spec.ts index 1b0c3d9eb04..aa59d645745 100644 --- a/libs/common/spec/models/domain/encString.spec.ts +++ b/libs/common/spec/models/domain/encString.spec.ts @@ -192,4 +192,12 @@ describe("EncString", () => { cryptoService.received().decryptToUtf8(encString, key); }); }); + + describe("toJSON", () => { + it("Should be represented by the encrypted string", () => { + const encString = new EncString(EncryptionType.AesCbc256_B64, "data", "iv"); + + expect(encString.toJSON()).toBe(encString.encryptedString); + }); + }); }); diff --git a/libs/common/spec/models/domain/folder.spec.ts b/libs/common/spec/models/domain/folder.spec.ts index c36caa80ba3..7ae36f9b391 100644 --- a/libs/common/spec/models/domain/folder.spec.ts +++ b/libs/common/spec/models/domain/folder.spec.ts @@ -1,4 +1,5 @@ import { FolderData } from "@bitwarden/common/models/data/folderData"; +import { EncString } from "@bitwarden/common/models/domain/encString"; import { Folder } from "@bitwarden/common/models/domain/folder"; import { mockEnc } from "../../utils"; @@ -38,4 +39,27 @@ describe("Folder", () => { revisionDate: new Date("2022-01-31T12:00:00.000Z"), }); }); + + describe("fromJSON", () => { + jest.mock("@bitwarden/common/models/domain/encString"); + const mockFromJson = (stub: any) => (stub + "_fromJSON") as any; + jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson); + + it("initializes nested objects", () => { + const revisionDate = new Date("2022-08-04T01:06:40.441Z"); + const actual = Folder.fromJSON({ + revisionDate: revisionDate.toISOString(), + name: "name", + id: "id", + }); + + const expected = { + revisionDate: revisionDate, + name: "name_fromJSON", + id: "id", + }; + + expect(actual).toMatchObject(expected); + }); + }); }); diff --git a/libs/common/spec/models/view/folderView.spec.ts b/libs/common/spec/models/view/folderView.spec.ts new file mode 100644 index 00000000000..50b8c2948d5 --- /dev/null +++ b/libs/common/spec/models/view/folderView.spec.ts @@ -0,0 +1,22 @@ +import { FolderView } from "@bitwarden/common/models/view/folderView"; + +describe("FolderView", () => { + describe("fromJSON", () => { + it("initializes nested objects", () => { + const revisionDate = new Date("2022-08-04T01:06:40.441Z"); + const actual = FolderView.fromJSON({ + revisionDate: revisionDate.toISOString(), + name: "name", + id: "id", + }); + + const expected = { + revisionDate: revisionDate, + name: "name", + id: "id", + }; + + expect(actual).toMatchObject(expected); + }); + }); +}); diff --git a/libs/common/src/abstractions/storage.service.ts b/libs/common/src/abstractions/storage.service.ts index 31fe14ddcfe..5cff9fdac60 100644 --- a/libs/common/src/abstractions/storage.service.ts +++ b/libs/common/src/abstractions/storage.service.ts @@ -6,3 +6,7 @@ export abstract class AbstractStorageService { abstract save(key: string, obj: T, options?: StorageOptions): Promise; abstract remove(key: string, options?: StorageOptions): Promise; } + +export abstract class AbstractCachedStorageService extends AbstractStorageService { + abstract getBypassCache(key: string, options?: StorageOptions): Promise; +} diff --git a/libs/common/src/models/domain/encString.ts b/libs/common/src/models/domain/encString.ts index a11ba3a58c6..46c267bb042 100644 --- a/libs/common/src/models/domain/encString.ts +++ b/libs/common/src/models/domain/encString.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { IEncrypted } from "@bitwarden/common/interfaces/IEncrypted"; import { CryptoService } from "../../abstractions/crypto.service"; @@ -21,80 +23,9 @@ export class EncString implements IEncrypted { mac?: string ) { if (data != null) { - // data and header - const encType = encryptedStringOrType as EncryptionType; - - if (iv != null) { - this.encryptedString = encType + "." + iv + "|" + data; - } else { - this.encryptedString = encType + "." + data; - } - - // mac - if (mac != null) { - this.encryptedString += "|" + mac; - } - - this.encryptionType = encType; - this.data = data; - this.iv = iv; - this.mac = mac; - - return; - } - - this.encryptedString = encryptedStringOrType as string; - if (!this.encryptedString) { - return; - } - - const headerPieces = this.encryptedString.split("."); - let encPieces: string[] = null; - - if (headerPieces.length === 2) { - try { - this.encryptionType = parseInt(headerPieces[0], null); - encPieces = headerPieces[1].split("|"); - } catch (e) { - return; - } + this.initFromData(encryptedStringOrType as EncryptionType, data, iv, mac); } else { - encPieces = this.encryptedString.split("|"); - this.encryptionType = - encPieces.length === 3 - ? EncryptionType.AesCbc128_HmacSha256_B64 - : EncryptionType.AesCbc256_B64; - } - - switch (this.encryptionType) { - case EncryptionType.AesCbc128_HmacSha256_B64: - case EncryptionType.AesCbc256_HmacSha256_B64: - if (encPieces.length !== 3) { - return; - } - - this.iv = encPieces[0]; - this.data = encPieces[1]; - this.mac = encPieces[2]; - break; - case EncryptionType.AesCbc256_B64: - if (encPieces.length !== 2) { - return; - } - - this.iv = encPieces[0]; - this.data = encPieces[1]; - break; - case EncryptionType.Rsa2048_OaepSha256_B64: - case EncryptionType.Rsa2048_OaepSha1_B64: - if (encPieces.length !== 1) { - return; - } - - this.data = encPieces[0]; - break; - default: - return; + this.initFromEncryptedString(encryptedStringOrType as string); } } @@ -133,4 +64,100 @@ export class EncString implements IEncrypted { get dataBytes(): ArrayBuffer { return this.data == null ? null : Utils.fromB64ToArray(this.data).buffer; } + + toJSON() { + return this.encryptedString; + } + + static fromJSON(obj: Jsonify): EncString { + return new EncString(obj); + } + + private initFromData(encType: EncryptionType, data: string, iv: string, mac: string) { + if (iv != null) { + this.encryptedString = encType + "." + iv + "|" + data; + } else { + this.encryptedString = encType + "." + data; + } + + // mac + if (mac != null) { + this.encryptedString += "|" + mac; + } + + this.encryptionType = encType; + this.data = data; + this.iv = iv; + this.mac = mac; + } + + private initFromEncryptedString(encryptedString: string) { + this.encryptedString = encryptedString as string; + if (!this.encryptedString) { + return; + } + + const { encType, encPieces } = this.parseEncryptedString(this.encryptedString); + this.encryptionType = encType; + + switch (encType) { + case EncryptionType.AesCbc128_HmacSha256_B64: + case EncryptionType.AesCbc256_HmacSha256_B64: + if (encPieces.length !== 3) { + return; + } + + this.iv = encPieces[0]; + this.data = encPieces[1]; + this.mac = encPieces[2]; + break; + case EncryptionType.AesCbc256_B64: + if (encPieces.length !== 2) { + return; + } + + this.iv = encPieces[0]; + this.data = encPieces[1]; + break; + case EncryptionType.Rsa2048_OaepSha256_B64: + case EncryptionType.Rsa2048_OaepSha1_B64: + if (encPieces.length !== 1) { + return; + } + + this.data = encPieces[0]; + break; + default: + return; + } + } + + private parseEncryptedString(encryptedString: string): { + encType: EncryptionType; + encPieces: string[]; + } { + const headerPieces = encryptedString.split("."); + let encType: EncryptionType; + let encPieces: string[] = null; + + if (headerPieces.length === 2) { + try { + encType = parseInt(headerPieces[0], null); + encPieces = headerPieces[1].split("|"); + } catch (e) { + return; + } + } else { + encPieces = encryptedString.split("|"); + encType = + encPieces.length === 3 + ? EncryptionType.AesCbc128_HmacSha256_B64 + : EncryptionType.AesCbc256_B64; + } + + return { + encType, + encPieces, + }; + } } diff --git a/libs/common/src/models/domain/folder.ts b/libs/common/src/models/domain/folder.ts index 8596e94d764..fcc700b0675 100644 --- a/libs/common/src/models/domain/folder.ts +++ b/libs/common/src/models/domain/folder.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { FolderData } from "../data/folderData"; import { FolderView } from "../view/folderView"; @@ -37,4 +39,9 @@ export class Folder extends Domain { null ); } + + static fromJSON(obj: Jsonify) { + const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate); + return Object.assign(new Folder(), obj, { name: EncString.fromJSON(obj.name), revisionDate }); + } } diff --git a/libs/common/src/models/view/folderView.ts b/libs/common/src/models/view/folderView.ts index 731acffba42..dcd12767054 100644 --- a/libs/common/src/models/view/folderView.ts +++ b/libs/common/src/models/view/folderView.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { Folder } from "../domain/folder"; import { ITreeNodeObject } from "../domain/treeNode"; @@ -16,4 +18,9 @@ export class FolderView implements View, ITreeNodeObject { this.id = f.id; this.revisionDate = f.revisionDate; } + + static fromJSON(obj: Jsonify) { + const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate); + return Object.assign(new FolderView(), obj, { revisionDate }); + } } diff --git a/libs/common/src/services/folder/folder.service.ts b/libs/common/src/services/folder/folder.service.ts index 4b3530cb817..2c598a70c1c 100644 --- a/libs/common/src/services/folder/folder.service.ts +++ b/libs/common/src/services/folder/folder.service.ts @@ -13,8 +13,8 @@ import { SymmetricCryptoKey } from "../../models/domain/symmetricCryptoKey"; import { FolderView } from "../../models/view/folderView"; export class FolderService implements InternalFolderServiceAbstraction { - private _folders: BehaviorSubject = new BehaviorSubject([]); - private _folderViews: BehaviorSubject = new BehaviorSubject([]); + protected _folders: BehaviorSubject = new BehaviorSubject([]); + protected _folderViews: BehaviorSubject = new BehaviorSubject([]); folders$ = this._folders.asObservable(); folderViews$ = this._folderViews.asObservable(); From 7e39867dae390d2b7d40f41b33d92d82477761e9 Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Tue, 16 Aug 2022 09:10:35 -0400 Subject: [PATCH 14/16] Import OrganizationBadgeModule in VaultModule (#3316) --- apps/web/src/app/vault/vault.module.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/vault/vault.module.ts b/apps/web/src/app/vault/vault.module.ts index e34be637e9c..8dc83d613d2 100644 --- a/apps/web/src/app/vault/vault.module.ts +++ b/apps/web/src/app/vault/vault.module.ts @@ -1,12 +1,13 @@ import { NgModule } from "@angular/core"; import { CiphersComponent } from "./ciphers.component"; +import { OrganizationBadgeModule } from "./organization-badge/organization-badge.module"; import { VaultSharedModule } from "./shared/vault-shared.module"; import { VaultRoutingModule } from "./vault-routing.module"; import { VaultComponent } from "./vault.component"; @NgModule({ - imports: [VaultSharedModule, VaultRoutingModule], + imports: [VaultSharedModule, VaultRoutingModule, OrganizationBadgeModule], declarations: [VaultComponent, CiphersComponent], exports: [VaultComponent], }) From 16c41b823b2abb3b40296283de61a135f5e51a9a Mon Sep 17 00:00:00 2001 From: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Date: Tue, 16 Aug 2022 08:55:57 -0700 Subject: [PATCH 15/16] [workflows] Decouple hotfix-rc branches (#3298) --- .github/workflows/build-browser.yml | 4 ++-- .github/workflows/build-cli.yml | 2 +- .github/workflows/build-desktop.yml | 22 +++++++++++----------- .github/workflows/build-web.yml | 16 ++++++++-------- .github/workflows/release-browser.yml | 4 ++-- .github/workflows/release-cli.yml | 4 ++-- .github/workflows/release-desktop.yml | 4 ++-- .github/workflows/release-web.yml | 4 ++-- 8 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index a1229db88a5..77303768f7a 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -16,7 +16,7 @@ on: branches: - 'master' - 'rc' - - 'hotfix-rc' + - 'hotfix-rc-browser' paths: - 'apps/browser/**' - 'libs/**' @@ -347,7 +347,7 @@ jobs: trigger-desktop-build: name: Trigger desktop build - if: ${{ (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/rc') || github.ref != 'refs/heads/hotfix-rc' }} + if: ${{ (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/rc') || github.ref != 'refs/heads/hotfix-rc-browser' }} runs-on: ubuntu-20.04 needs: - build diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 7a355f997a3..96ccea4ef86 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -17,7 +17,7 @@ on: branches: - 'master' - 'rc' - - 'hotfix-rc' + - 'hotfix-rc-cli' paths: - 'apps/cli/**' - 'libs/**' diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 78a7c5f79da..16579580b55 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -16,7 +16,7 @@ on: branches: - 'master' - 'rc' - - 'hotfix-rc' + - 'hotfix-rc-desktop' paths: - 'apps/desktop/**' - 'libs/**' @@ -121,7 +121,7 @@ jobs: echo "::set-output name=rc_branch_exists::0" fi - if [[ $(git ls-remote --heads origin hotfix-rc) ]]; then + if [[ $(git ls-remote --heads origin hotfix-rc-desktop) ]]; then echo "::set-output name=hotfix_branch_exists::1" else echo "::set-output name=hotfix_branch_exists::0" @@ -744,13 +744,13 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: npm run build - - name: Download artifact from hotfix-rc - if: github.ref == 'refs/heads/hotfix-rc' + - name: Download artifact from hotfix-rc-desktop + if: github.ref == 'refs/heads/hotfix-rc-desktop' uses: dawidd6/action-download-artifact@b2abf1705491048a2d7074f7d90513044fd25d39 # v2.19.0 with: workflow: build-browser.yml workflow_conclusion: success - branch: hotfix-rc + branch: hotfix-rc-desktop path: ${{ github.workspace }}/browser-build-artifacts - name: Download artifact from rc @@ -763,7 +763,7 @@ jobs: path: ${{ github.workspace }}/browser-build-artifacts - name: Download artifact from master - if: ${{ github.ref != 'refs/heads/rc' && github.ref != 'refs/heads/hotfix-rc' }} + if: ${{ github.ref != 'refs/heads/rc' && github.ref != 'refs/heads/hotfix-rc-desktop' }} uses: dawidd6/action-download-artifact@b2abf1705491048a2d7074f7d90513044fd25d39 # v2.19.0 with: workflow: build-browser.yml @@ -962,13 +962,13 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: npm run build - - name: Download artifact from hotfix-rc - if: github.ref == 'refs/heads/hotfix-rc' + - name: Download artifact from hotfix-rc-desktop + if: github.ref == 'refs/heads/hotfix-rc-desktop' uses: dawidd6/action-download-artifact@b2abf1705491048a2d7074f7d90513044fd25d39 # v2.19.0 with: workflow: build-browser.yml workflow_conclusion: success - branch: hotfix-rc + branch: hotfix-rc-desktop path: ${{ github.workspace }}/browser-build-artifacts - name: Download artifact from rc @@ -981,7 +981,7 @@ jobs: path: ${{ github.workspace }}/browser-build-artifacts - name: Download artifact from master - if: ${{ github.ref != 'refs/heads/rc' && github.ref != 'refs/heads/hotfix-rc' }} + if: ${{ github.ref != 'refs/heads/rc' && github.ref != 'refs/heads/hotfix-rc-desktop' }} uses: dawidd6/action-download-artifact@b2abf1705491048a2d7074f7d90513044fd25d39 # v2.19.0 with: workflow: build-browser.yml @@ -1022,7 +1022,7 @@ jobs: && needs.setup.outputs.rc_branch_exists == 0 && needs.setup.outputs.hotfix_branch_exists == 0) || (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0) - || github.ref == 'refs/heads/hotfix-rc' + || github.ref == 'refs/heads/hotfix-rc-desktop' run: npm run upload:mas diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 4e4a398c71b..39784468974 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -17,7 +17,7 @@ on: branches: - 'master' - 'rc' - - 'hotfix-rc' + - 'hotfix-rc-web' paths: - 'apps/web/**' - 'libs/**' @@ -182,7 +182,7 @@ jobs: echo "GitHub event: $GITHUB_EVENT" - name: Setup DCT - if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc' + if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-web' id: setup-dct uses: bitwarden/gh-actions/setup-docker-trust@a8c384a05a974c05c48374c818b004be221d43ff with: @@ -225,11 +225,11 @@ jobs: run: docker tag bitwarden/web bitwarden/web:dev - name: Tag hotfix branch - if: github.ref == 'refs/heads/hotfix-rc' - run: docker tag bitwarden/web bitwarden/web:hotfix-rc + if: github.ref == 'refs/heads/hotfix-rc-web' + run: docker tag bitwarden/web bitwarden/web:hotfix-rc-web - name: List Docker images - if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc' + if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-web' run: docker images - name: Push rc image @@ -247,14 +247,14 @@ jobs: DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }} - name: Push hotfix image - if: github.ref == 'refs/heads/hotfix-rc' - run: docker push bitwarden/web:hotfix-rc + if: github.ref == 'refs/heads/hotfix-rc-web' + run: docker push bitwarden/web:hotfix-rc-web env: DOCKER_CONTENT_TRUST: 1 DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ steps.setup-dct.outputs.dct-delegate-repo-passphrase }} - name: Log out of Docker - if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc' + if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-web' run: | docker logout echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV diff --git a/.github/workflows/release-browser.yml b/.github/workflows/release-browser.yml index 6599212f340..7279d3b08b4 100644 --- a/.github/workflows/release-browser.yml +++ b/.github/workflows/release-browser.yml @@ -31,9 +31,9 @@ jobs: - name: Branch check if: ${{ github.event.inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ $GITHUB_REF != refs/heads/hotfix-rc ]]; then + if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ $GITHUB_REF != refs/heads/hotfix-rc-browser ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'rc' or 'hotfix-rc-browser' branches" echo "===================================" exit 1 fi diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 66c324bdd6e..78d645e8a26 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -47,9 +47,9 @@ jobs: - name: Branch check if: ${{ github.event.inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ $GITHUB_REF != refs/heads/hotfix-rc ]]; then + if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ $GITHUB_REF != refs/heads/hotfix-rc-cli ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'rc' or 'hotfix-rc-cli' branches" echo "===================================" exit 1 fi diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index dad38a80ba1..84cbdf17f23 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -42,9 +42,9 @@ jobs: - name: Branch check if: ${{ github.event.inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ $GITHUB_REF != refs/heads/hotfix-rc ]]; then + if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ $GITHUB_REF != refs/heads/hotfix-rc-desktop ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'rc' or 'hotfix-rc-desktop' branches" echo "===================================" exit 1 fi diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index 7dd828cc5f7..aae053c35c9 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -28,9 +28,9 @@ jobs: - name: Branch check if: ${{ github.event.inputs.release_type != 'Dry Run' }} run: | - if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ $GITHUB_REF != "refs/heads/hotfix-rc" ]]; then + if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ $GITHUB_REF != "refs/heads/hotfix-rc-web" ]]; then echo "===================================" - echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches" + echo "[!] Can only release from the 'rc' or 'hotfix-rc-web' branches" echo "===================================" exit 1 fi From 7c3facec80937ca2865f7f823ae127858bbf1c31 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Tue, 16 Aug 2022 09:59:50 -0600 Subject: [PATCH 16/16] Split session key and synced item property key (#3317) --- .../browser-session.decorator.ts | 2 +- .../session-sync.decorator.spec.ts | 3 ++- .../session-sync.decorator.ts | 3 ++- .../session-syncer.spec.ts | 22 ++++++++++--------- .../session-sync-observable/session-syncer.ts | 6 ++--- .../sync-item-metadata.ts | 3 ++- .../synced-item-metadata.spec.ts | 13 +++++++---- 7 files changed, 31 insertions(+), 21 deletions(-) diff --git a/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.ts b/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.ts index bee93173d19..73cdf767357 100644 --- a/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.ts +++ b/apps/browser/src/decorators/session-sync-observable/browser-session.decorator.ts @@ -39,7 +39,7 @@ export function browserSession>(constructor: TCto } buildSyncer(metadata: SyncedItemMetadata, stateService: StateService) { - const syncer = new SessionSyncer((this as any)[metadata.key], stateService, metadata); + const syncer = new SessionSyncer((this as any)[metadata.propertyKey], stateService, metadata); syncer.init(); return syncer; } diff --git a/apps/browser/src/decorators/session-sync-observable/session-sync.decorator.spec.ts b/apps/browser/src/decorators/session-sync-observable/session-sync.decorator.spec.ts index d1cb8e7d155..c82f2700213 100644 --- a/apps/browser/src/decorators/session-sync-observable/session-sync.decorator.spec.ts +++ b/apps/browser/src/decorators/session-sync-observable/session-sync.decorator.spec.ts @@ -14,7 +14,8 @@ describe("sessionSync decorator", () => { const testClass = new TestClass(); expect((testClass as any).__syncedItemMetadata).toEqual([ expect.objectContaining({ - key: "TestClass_testProperty", + propertyKey: "testProperty", + sessionKey: "TestClass_testProperty", ctor: ctor, initializer: initializer, }), diff --git a/apps/browser/src/decorators/session-sync-observable/session-sync.decorator.ts b/apps/browser/src/decorators/session-sync-observable/session-sync.decorator.ts index e9887c00850..df0764528f7 100644 --- a/apps/browser/src/decorators/session-sync-observable/session-sync.decorator.ts +++ b/apps/browser/src/decorators/session-sync-observable/session-sync.decorator.ts @@ -42,7 +42,8 @@ export function sessionSync(buildOptions: BuildOptions) { } p.__syncedItemMetadata.push({ - key: `${prototype.constructor.name}_${propertyKey}`, + propertyKey, + sessionKey: `${prototype.constructor.name}_${propertyKey}`, ctor: buildOptions.ctor, initializer: buildOptions.initializer, initializeAsArray: buildOptions.initializeAsArray, diff --git a/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts b/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts index b08ee85647d..40ba412cd4a 100644 --- a/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts +++ b/apps/browser/src/decorators/session-sync-observable/session-syncer.spec.ts @@ -7,8 +7,9 @@ import { StateService } from "../../services/abstractions/state.service"; import { SessionSyncer } from "./session-syncer"; describe("session syncer", () => { - const key = "Test__behaviorSubject"; - const metaData = { key, initializer: (s: string) => s }; + const propertyKey = "behaviorSubject"; + const sessionKey = "Test__" + propertyKey; + const metaData = { propertyKey, sessionKey, initializer: (s: string) => s }; let stateService: MockProxy; let sut: SessionSyncer; let behaviorSubject: BehaviorSubject; @@ -40,18 +41,19 @@ describe("session syncer", () => { it("should create if either ctor or initializer is provided", () => { expect( - new SessionSyncer(behaviorSubject, stateService, { key: key, ctor: String }) + new SessionSyncer(behaviorSubject, stateService, { propertyKey, sessionKey, ctor: String }) ).toBeDefined(); expect( new SessionSyncer(behaviorSubject, stateService, { - key: key, + propertyKey, + sessionKey, initializer: (s: any) => s, }) ).toBeDefined(); }); it("should throw if neither ctor or initializer is provided", () => { expect(() => { - new SessionSyncer(behaviorSubject, stateService, { key: key }); + new SessionSyncer(behaviorSubject, stateService, { propertyKey, sessionKey }); }).toThrowError("ctor or initializer must be provided"); }); }); @@ -96,7 +98,7 @@ describe("session syncer", () => { // await finishing of fire-and-forget operation await new Promise((resolve) => setTimeout(resolve, 100)); expect(stateService.setInSessionMemory).toHaveBeenCalledTimes(1); - expect(stateService.setInSessionMemory).toHaveBeenCalledWith(key, "test"); + expect(stateService.setInSessionMemory).toHaveBeenCalledWith(sessionKey, "test"); }); it("should update sessionSyncers in other contexts", async () => { @@ -104,7 +106,7 @@ describe("session syncer", () => { await new Promise((resolve) => setTimeout(resolve, 100)); expect(sendMessageSpy).toHaveBeenCalledTimes(1); - expect(sendMessageSpy).toHaveBeenCalledWith(`${key}_update`, { id: sut.id }); + expect(sendMessageSpy).toHaveBeenCalledWith(`${sessionKey}_update`, { id: sut.id }); }); }); @@ -131,7 +133,7 @@ describe("session syncer", () => { }); it("should ignore messages from itself", async () => { - await sut.updateFromMessage({ command: `${key}_update`, id: sut.id }); + await sut.updateFromMessage({ command: `${sessionKey}_update`, id: sut.id }); expect(stateService.getFromSessionMemory).not.toHaveBeenCalled(); expect(nextSpy).not.toHaveBeenCalled(); @@ -140,10 +142,10 @@ describe("session syncer", () => { it("should update from message on emit from another instance", async () => { stateService.getFromSessionMemory.mockResolvedValue("test"); - await sut.updateFromMessage({ command: `${key}_update`, id: "different_id" }); + await sut.updateFromMessage({ command: `${sessionKey}_update`, id: "different_id" }); expect(stateService.getFromSessionMemory).toHaveBeenCalledTimes(1); - expect(stateService.getFromSessionMemory).toHaveBeenCalledWith(key); + expect(stateService.getFromSessionMemory).toHaveBeenCalledWith(sessionKey); expect(nextSpy).toHaveBeenCalledTimes(1); expect(nextSpy).toHaveBeenCalledWith("test"); diff --git a/apps/browser/src/decorators/session-sync-observable/session-syncer.ts b/apps/browser/src/decorators/session-sync-observable/session-syncer.ts index 0bfc56d5211..80eb07289f4 100644 --- a/apps/browser/src/decorators/session-sync-observable/session-syncer.ts +++ b/apps/browser/src/decorators/session-sync-observable/session-syncer.ts @@ -62,18 +62,18 @@ export class SessionSyncer { if (message.command != this.updateMessageCommand || message.id === this.id) { return; } - const keyValuePair = await this.stateService.getFromSessionMemory(this.metaData.key); + const keyValuePair = await this.stateService.getFromSessionMemory(this.metaData.sessionKey); const value = SyncedItemMetadata.buildFromKeyValuePair(keyValuePair, this.metaData); this.ignoreNextUpdate = true; this.behaviorSubject.next(value); } private async updateSession(value: any) { - await this.stateService.setInSessionMemory(this.metaData.key, value); + await this.stateService.setInSessionMemory(this.metaData.sessionKey, value); await BrowserApi.sendMessage(this.updateMessageCommand, { id: this.id }); } private get updateMessageCommand() { - return `${this.metaData.key}_update`; + return `${this.metaData.sessionKey}_update`; } } diff --git a/apps/browser/src/decorators/session-sync-observable/sync-item-metadata.ts b/apps/browser/src/decorators/session-sync-observable/sync-item-metadata.ts index 7f632c962ea..e225db61967 100644 --- a/apps/browser/src/decorators/session-sync-observable/sync-item-metadata.ts +++ b/apps/browser/src/decorators/session-sync-observable/sync-item-metadata.ts @@ -1,5 +1,6 @@ export class SyncedItemMetadata { - key: string; + propertyKey: string; + sessionKey: string; ctor?: new () => any; initializer?: (keyValuePair: any) => any; initializeAsArray?: boolean; diff --git a/apps/browser/src/decorators/session-sync-observable/synced-item-metadata.spec.ts b/apps/browser/src/decorators/session-sync-observable/synced-item-metadata.spec.ts index c8e819dbe6e..da65be04903 100644 --- a/apps/browser/src/decorators/session-sync-observable/synced-item-metadata.spec.ts +++ b/apps/browser/src/decorators/session-sync-observable/synced-item-metadata.spec.ts @@ -1,6 +1,7 @@ import { SyncedItemMetadata } from "./sync-item-metadata"; describe("build from key value pair", () => { + const propertyKey = "propertyKey"; const key = "key"; const initializer = (s: any) => "used initializer"; class TestClass {} @@ -10,7 +11,8 @@ describe("build from key value pair", () => { const actual = SyncedItemMetadata.buildFromKeyValuePair( {}, { - key: "key", + propertyKey, + sessionKey: "key", initializer: initializer, } ); @@ -21,7 +23,8 @@ describe("build from key value pair", () => { it("should call ctor if provided", () => { const expected = { provided: "value" }; const actual = SyncedItemMetadata.buildFromKeyValuePair(expected, { - key: key, + propertyKey, + sessionKey: key, ctor: ctor, }); @@ -33,7 +36,8 @@ describe("build from key value pair", () => { const actual = SyncedItemMetadata.buildFromKeyValuePair( {}, { - key: key, + propertyKey, + sessionKey: key, initializer: initializer, ctor: ctor, } @@ -44,7 +48,8 @@ describe("build from key value pair", () => { it("should honor initialize as array", () => { const actual = SyncedItemMetadata.buildFromKeyValuePair([1, 2], { - key: key, + propertyKey, + sessionKey: key, initializer: initializer, initializeAsArray: true, });