1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 09:13:33 +00:00

Merge branch 'master' into feature/org-admin-refresh

This commit is contained in:
Shane Melton
2022-08-16 11:28:20 -07:00
250 changed files with 3843 additions and 1369 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -17,7 +17,7 @@ on:
branches:
- 'master'
- 'rc'
- 'hotfix-rc'
- 'hotfix-rc-cli'
paths:
- 'apps/cli/**'
- 'libs/**'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,4 @@
{
"dev_flags": {},
"flags": {}
}

View File

@@ -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,
};

View File

@@ -0,0 +1,6 @@
{
"devFlags": {
"storeSessionDecrypted": false
},
"flags": {}
}

View File

@@ -0,0 +1,3 @@
{
"flags": {}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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": "за да върнете предварително зададените настройки"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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": "برای بازنشانی به تنظیمات از پیش پیکربندی شده"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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."
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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": "すると初期設定に戻します"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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ń"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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": "для сброса к предварительно настроенным параметрам"
}
}

View File

@@ -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"
}
}

View File

@@ -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í"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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": "щоб скинути налаштування"
}
}

View File

@@ -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"
}
}

View File

@@ -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": "重置为预设设置"
}
}

View File

@@ -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": "重設為預設設定"
}
}

View File

@@ -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";

View File

@@ -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,

View File

@@ -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();
});
});

View File

@@ -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);
};
};
}

View File

@@ -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();
});
});
});

View File

@@ -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<TCtor extends Constructor<any>>(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.propertyKey], stateService, metadata);
syncer.init();
return syncer;
}
};
}

View File

@@ -0,0 +1,2 @@
export { browserSession } from "./browser-session.decorator";
export { sessionSync } from "./session-sync.decorator";

View File

@@ -0,0 +1,7 @@
import { SessionSyncer } from "./session-syncer";
import { SyncedItemMetadata } from "./sync-item-metadata";
export interface SessionStorable {
__syncedItemMetadata: SyncedItemMetadata[];
__sessionSyncers: SessionSyncer[];
}

View File

@@ -0,0 +1,24 @@
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({
propertyKey: "testProperty",
sessionKey: "TestClass_testProperty",
ctor: ctor,
initializer: initializer,
}),
]);
});
});

View File

@@ -0,0 +1,52 @@
import { Jsonify } from "type-fest";
import { SessionStorable } from "./session-storable";
class BuildOptions<T> {
ctor?: new () => T;
initializer?: (keyValuePair: Jsonify<T>) => 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<T>(buildOptions: BuildOptions<T>) {
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({
propertyKey,
sessionKey: `${prototype.constructor.name}_${propertyKey}`,
ctor: buildOptions.ctor,
initializer: buildOptions.initializer,
initializeAsArray: buildOptions.initializeAsArray,
});
};
}

View File

@@ -0,0 +1,158 @@
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 propertyKey = "behaviorSubject";
const sessionKey = "Test__" + propertyKey;
const metaData = { propertyKey, sessionKey, initializer: (s: string) => s };
let stateService: MockProxy<StateService>;
let sut: SessionSyncer;
let behaviorSubject: BehaviorSubject<string>;
beforeEach(() => {
behaviorSubject = new BehaviorSubject<string>("");
jest.spyOn(chrome.runtime, "getManifest").mockReturnValue({
name: "bitwarden-test",
version: "0.0.0",
manifest_version: 3,
});
stateService = mock<StateService>();
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, { propertyKey, sessionKey, ctor: String })
).toBeDefined();
expect(
new SessionSyncer(behaviorSubject, stateService, {
propertyKey,
sessionKey,
initializer: (s: any) => s,
})
).toBeDefined();
});
it("should throw if neither ctor or initializer is provided", () => {
expect(() => {
new SessionSyncer(behaviorSubject, stateService, { propertyKey, sessionKey });
}).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(sessionKey, "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(`${sessionKey}_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: `${sessionKey}_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: `${sessionKey}_update`, id: "different_id" });
expect(stateService.getFromSessionMemory).toHaveBeenCalledTimes(1);
expect(stateService.getFromSessionMemory).toHaveBeenCalledWith(sessionKey);
expect(nextSpy).toHaveBeenCalledTimes(1);
expect(nextSpy).toHaveBeenCalledWith("test");
expect(behaviorSubject.value).toBe("test");
// Expect no circular messaging
expect(sendMessageSpy).toHaveBeenCalledTimes(0);
});
});
});

View File

@@ -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<any>,
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.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.sessionKey, value);
await BrowserApi.sendMessage(this.updateMessageCommand, { id: this.id });
}
private get updateMessageCommand() {
return `${this.metaData.sessionKey}_update`;
}
}

View File

@@ -0,0 +1,23 @@
export class SyncedItemMetadata {
propertyKey: string;
sessionKey: 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);
}
}

View File

@@ -0,0 +1,59 @@
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 {}
const ctor = TestClass;
it("should call initializer if provided", () => {
const actual = SyncedItemMetadata.buildFromKeyValuePair(
{},
{
propertyKey,
sessionKey: "key",
initializer: initializer,
}
);
expect(actual).toEqual("used initializer");
});
it("should call ctor if provided", () => {
const expected = { provided: "value" };
const actual = SyncedItemMetadata.buildFromKeyValuePair(expected, {
propertyKey,
sessionKey: 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(
{},
{
propertyKey,
sessionKey: key,
initializer: initializer,
ctor: ctor,
}
);
expect(actual).toEqual("used initializer");
});
it("should honor initialize as array", () => {
const actual = SyncedItemMetadata.buildFromKeyValuePair([1, 2], {
propertyKey,
sessionKey: key,
initializer: initializer,
initializeAsArray: true,
});
expect(actual).toEqual(["used initializer", "used initializer"]);
});
});

41
apps/browser/src/flags.ts Normal file
View File

@@ -0,0 +1,41 @@
function getFlags<T>(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<Flags>(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<DevFlags>(process.env.DEV_FLAGS);
return devFlags[flag] == null || devFlags[flag];
}

View File

@@ -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"

View File

@@ -7,6 +7,8 @@ import { BrowserGroupingsComponentState } from "src/models/browserGroupingsCompo
import { BrowserSendComponentState } from "src/models/browserSendComponentState";
export abstract class StateService extends BaseStateServiceAbstraction<Account> {
abstract getFromSessionMemory<T>(key: string): Promise<T>;
abstract setInSessionMemory(key: string, value: any): Promise<void>;
getBrowserGroupingComponentState: (
options?: StorageOptions
) => Promise<BrowserGroupingsComponentState>;

View File

@@ -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);
}
}

View File

@@ -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<Folder[]>;
@sessionSync({ initializer: FolderView.fromJSON, initializeAsArray: true })
protected _folderViews: BehaviorSubject<FolderView[]>;
}

View File

@@ -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<string, any>();
export class LocalBackedSessionStorageService extends AbstractCachedStorageService {
private cache = new Map<string, unknown>();
private localStorage = new BrowserLocalStorageService();
private sessionStorage = new BrowserMemoryStorageService();
@@ -26,23 +29,27 @@ export class LocalBackedSessionStorageService extends AbstractStorageService {
async get<T>(key: string): Promise<T> {
if (this.cache.has(key)) {
return this.cache.get(key);
return this.cache.get(key) as T;
}
return await this.getBypassCache(key);
}
async getBypassCache<T>(key: string): Promise<T> {
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<boolean> {
return (await this.get(key)) != null;
}
async save(key: string, obj: any): Promise<void> {
async save<T>(key: string, obj: T): Promise<void> {
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<any> {
async getLocalSession(encKey: SymmetricCryptoKey): Promise<Record<string, unknown>> {
const local = await this.localStorage.get<string>(keys.sessionKey);
if (local == null) {
return null;
}
if (devFlagEnabled("storeSessionDecrypted")) {
return local as any as Record<string, unknown>;
}
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<string, unknown>, key: SymmetricCryptoKey) {
if (devFlagEnabled("storeSessionDecrypted")) {
await this.setDecryptedLocalSession(session);
} else {
await this.setEncryptedLocalSession(session, key);
}
}
@devFlag("storeSessionDecrypted")
async setDecryptedLocalSession(session: Record<string, unknown>): Promise<void> {
// 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<string, unknown>, 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<SymmetricCryptoKey> {
let storedKey = (await this.sessionStorage.get(keys.encKey)) as SymmetricCryptoKey;
let storedKey = await this.sessionStorage.get<SymmetricCryptoKey>(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<void> {

View File

@@ -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<AbstractStorageService>;
let diskStorageService: SubstituteOf<AbstractStorageService>;
let memoryStorageService: SubstituteOf<AbstractStorageService>;
let logService: SubstituteOf<LogService>;
let stateMigrationService: SubstituteOf<StateMigrationService>;
let stateFactory: SubstituteOf<StateFactory<GlobalState, Account>>;
@@ -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,6 +46,43 @@ describe("Browser State Service", () => {
profile: { userId: userId },
});
state.activeUserId = userId;
});
describe("direct memory storage access", () => {
let memoryStorageService: AbstractCachedStorageService;
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("state methods", () => {
let memoryStorageService: SubstituteOf<AbstractStorageService>;
beforeEach(() => {
memoryStorageService = Substitute.for();
const stateGetter = (key: string) => Promise.resolve(JSON.parse(JSON.stringify(state)));
memoryStorageService.get("state").mimicks(stateGetter);
@@ -107,3 +146,4 @@ describe("Browser State Service", () => {
});
});
});
});

View File

@@ -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<GlobalState, Account>
implements StateServiceAbstraction
{
async getFromSessionMemory<T>(key: string): Promise<T> {
return this.memoryStorageService instanceof AbstractCachedStorageService
? await this.memoryStorageService.getBypassCache<T>(key)
: await this.memoryStorageService.get<T>(key);
}
async setInSessionMemory(key: string, value: any): Promise<void> {
await this.memoryStorageService.save(key, value);
}
async addAccount(account: Account) {
// Apply browser overrides to default account values
account = new Account(account);

View File

@@ -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: {
const storage = {
local: {
set,
get,
remove,
set: jest.fn(),
get: jest.fn(),
remove: jest.fn(),
QUOTA_BYTES,
getBytesInUse,
clear,
getBytesInUse: jest.fn(),
clear: jest.fn(),
},
session: {
set,
get,
has,
remove,
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;

View File

@@ -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 = {

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-console */
function load(envName) {
return {
...loadConfig(envName),

View File

@@ -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",

View File

@@ -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."

View File

@@ -1979,6 +1979,9 @@
"apiKey": {
"message": "مفتاح API"
},
"premiumSubcriptionRequired": {
"message": "Premium subscription required"
},
"organizationIsDisabled": {
"message": "تم تعطيل المؤسسة."
},

View File

@@ -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ı."
},

View File

@@ -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."
},

View File

@@ -1979,6 +1979,9 @@
"apiKey": {
"message": "Ключ за API"
},
"premiumSubcriptionRequired": {
"message": "Изисква се платен абонамент"
},
"organizationIsDisabled": {
"message": "Организацията е изключена."
},

View File

@@ -1979,6 +1979,9 @@
"apiKey": {
"message": "API Key"
},
"premiumSubcriptionRequired": {
"message": "Premium subscription required"
},
"organizationIsDisabled": {
"message": "Organization is disabled."
},

View File

@@ -1979,6 +1979,9 @@
"apiKey": {
"message": "API Key"
},
"premiumSubcriptionRequired": {
"message": "Premium subscription required"
},
"organizationIsDisabled": {
"message": "Organization is disabled."
},

View File

@@ -1979,6 +1979,9 @@
"apiKey": {
"message": "Clau de l'API"
},
"premiumSubcriptionRequired": {
"message": "Cal una subscripció premium"
},
"organizationIsDisabled": {
"message": "L'organització està inhabilitada."
},

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