1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-01-23 21:31:29 +01:00

[PM-5189] Incorporating work done for inline menu field qualification

This commit is contained in:
Cesar Gonzalez 2024-06-17 06:18:18 -05:00
commit ede74bc72d
No known key found for this signature in database
GPG Key ID: 3381A5457F8CCECF
334 changed files with 6302 additions and 4270 deletions

View File

@ -186,17 +186,10 @@ jobs:
# path: browser-source/apps/browser/dist/dist-opera-mv3.zip
# if-no-files-found: error
- name: Upload Chrome artifact
- name: Upload Chrome MV3 artifact
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: dist-chrome-${{ env._BUILD_NUMBER }}.zip
path: browser-source/apps/browser/dist/dist-chrome.zip
if-no-files-found: error
- name: Upload Chrome MV3 artifact (DO NOT USE FOR PROD)
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: DO-NOT-USE-FOR-PROD-dist-chrome-MV3-${{ env._BUILD_NUMBER }}.zip
name: dist-chrome-MV3-${{ env._BUILD_NUMBER }}.zip
path: browser-source/apps/browser/dist/dist-chrome-mv3.zip
if-no-files-found: error

View File

@ -132,7 +132,7 @@ jobs:
PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }}
run: |
mv browser-source.zip browser-source-$PACKAGE_VERSION.zip
mv dist-chrome.zip dist-chrome-$PACKAGE_VERSION.zip
mv dist-chrome-mv3.zip dist-chrome-$PACKAGE_VERSION.zip
mv dist-opera.zip dist-opera-$PACKAGE_VERSION.zip
mv dist-firefox.zip dist-firefox-$PACKAGE_VERSION.zip
mv dist-edge.zip dist-edge-$PACKAGE_VERSION.zip

View File

@ -1,6 +1,6 @@
{
"name": "@bitwarden/browser",
"version": "2024.6.0",
"version": "2024.6.1",
"scripts": {
"build": "cross-env MANIFEST_VERSION=3 webpack",
"build:mv2": "webpack",

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "الهوية"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "سجل كلمة المرور"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -393,10 +393,10 @@
"message": "Sevimlilərdən sil"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
"message": "Element sevimlilərə əlavə edildi"
},
"itemRemovedFromFavorites": {
"message": "Item removed from favorites"
"message": "Element sevimlilərdən çıxarıldı"
},
"notes": {
"message": "Notlar"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Kimlik"
},
"newItemHeader": {
"message": "Yeni $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "$TYPE$ - düzəliş et",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Parol tarixçəsi"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Filtrləri təmizləyin və ya başqa bir axtarış terminini sınayın"
},
"copyInfoLabel": {
"message": "$ITEMNAME$ elementlərini kopyala",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Məlumatları kopyala - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "$ITEMNAME$ notunu kopyala",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Notu kopyala - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Avto-doldur - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "Kopyalanacaq dəyər yoxdur"
},
"assignCollections": {
"message": "Kolleksiyaları təyin et"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Пасведчанне"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Гісторыя пароляў"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -393,10 +393,10 @@
"message": "Изваждане от любими"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
"message": "Елементът е добавен към любимите"
},
"itemRemovedFromFavorites": {
"message": "Item removed from favorites"
"message": "Елементът е премахнат от любимите"
},
"notes": {
"message": "Бележки"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Самоличност"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Редактиране на $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Хронология на паролата"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Изчистете филтрите или опитайте да търсите нещо друго"
},
"copyInfoLabel": {
"message": "Копиране на информацията, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Копиране на информацията $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Копиране на бележката, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Копиране на бележката $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Авт. попълване $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "Няма стойности за копиране"
},
"assignCollections": {
"message": "Свързване на колекции"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "পরিচয়"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "পাসওয়ার্ড ইতিহাস"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identity"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Password history"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identitat"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Historial de les contrasenyes"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -393,10 +393,10 @@
"message": "Unfavorite"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
"message": "Položka byla přidána do oblíbených"
},
"itemRemovedFromFavorites": {
"message": "Item removed from favorites"
"message": "Položka byla odebrána z oblíbených"
},
"notes": {
"message": "Poznámky"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identita"
},
"newItemHeader": {
"message": "Nové $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Upravit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Historie hesel"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Vymažte filtry nebo zkuste jiný hledaný výraz"
},
"copyInfoLabel": {
"message": "Kopírovat informace, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Kopírovat informace - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Kopírovat poznámku, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Kopírovat poznámku - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Automatické vyplnění - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "Žádné hodnoty ke zkopírování"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Hunaniaeth"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Hanes cyfrineiriau"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identitet"
},
"newItemHeader": {
"message": "Ny $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Redigér $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Adgangskodehistorik"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Ryd filtre eller prøv med et andet søgeord"
},
"copyInfoLabel": {
"message": "Kopiér info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Kopiér info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Kopiér notat, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Kopiér notat - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Autoudfyld - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "Ingen værdier at kopiere"
},
"assignCollections": {
"message": "Tildel samlinger"
},

View File

@ -390,13 +390,13 @@
"message": "Favoriten"
},
"unfavorite": {
"message": "Unfavorite"
"message": "Aus Favoriten entfernen"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
"message": "Eintrag zu Favoriten hinzugefügt"
},
"itemRemovedFromFavorites": {
"message": "Item removed from favorites"
"message": "Eintrag aus Favoriten entfernt"
},
"notes": {
"message": "Notizen"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identität"
},
"newItemHeader": {
"message": "Neue $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "$TYPE$ bearbeiten",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Passwortverlauf"
},
@ -3305,18 +3323,8 @@
"clearFiltersOrTryAnother": {
"message": "Entferne die Filter oder versuche einen anderen Suchbegriff"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"message": "Information kopieren - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
@ -3325,18 +3333,8 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"message": "Notiz kopieren - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
@ -3346,7 +3344,7 @@
}
},
"moreOptionsLabel": {
"message": "More options, $ITEMNAME$",
"message": "Weitere Optionen, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with more options for an item.",
"placeholders": {
"itemname": {
@ -3356,7 +3354,7 @@
}
},
"moreOptionsTitle": {
"message": "More options - $ITEMNAME$",
"message": "Weitere Optionen - $ITEMNAME$",
"description": "Title for a button that opens a menu with more options for an item.",
"placeholders": {
"itemname": {
@ -3366,7 +3364,7 @@
}
},
"viewItemTitle": {
"message": "View item - $ITEMNAME$",
"message": "Eintrag anzeigen - $ITEMNAME$",
"description": "Title for a link that opens a view for an item.",
"placeholders": {
"itemname": {
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-Ausfüllen - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "Keine Werte zum Kopieren"
},
"assignCollections": {
"message": "Sammlungen zuweisen"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Ταυτότητα"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Ιστορικό Κωδικού"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -16,6 +16,12 @@
"createAccount": {
"message": "Create account"
},
"setAStrongPassword": {
"message": "Set a strong password"
},
"finishCreatingYourAccountBySettingAPassword": {
"message": "Finish creating your account by setting a password"
},
"login": {
"message": "Log in"
},
@ -1780,6 +1786,21 @@
"masterPasswordPolicyRequirementsNotMet": {
"message": "Your new master password does not meet the policy requirements."
},
"receiveMarketingEmails": {
"message": "Get emails from Bitwarden for announcements, advice, and research opportunities."
},
"unsubscribe": {
"message": "Unsubscribe"
},
"atAnyTime": {
"message": "at any time."
},
"byContinuingYouAgreeToThe": {
"message": "By continuing, you agree to the"
},
"and": {
"message": "and"
},
"acceptPolicies": {
"message": "By checking this box you agree to the following:"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identity"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Password history"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -393,10 +393,10 @@
"message": "Unfavourite"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
"message": "Item added to favourites"
},
"itemRemovedFromFavorites": {
"message": "Item removed from favorites"
"message": "Item removed from favourites"
},
"notes": {
"message": "Notes"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identity"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Password history"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -393,10 +393,10 @@
"message": "Eliminar favorito"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
"message": "Elemento añadido a favoritos"
},
"itemRemovedFromFavorites": {
"message": "Item removed from favorites"
"message": "Elemento eliminado de favoritos"
},
"notes": {
"message": "Notas"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identidad"
},
"newItemHeader": {
"message": "Nuevo $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Editar $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Historial de contraseñas"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Limpia los filtros o prueba otro término de búsqueda"
},
"copyInfoLabel": {
"message": "Copiar información, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copiar información - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copiar nota, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copiar nota - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Autocompletar - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No hay valores para copiar"
},
"assignCollections": {
"message": "Asignar colecciones"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identiteet"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Paroolide ajalugu"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identitatea"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Pasahitz historia"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "هویت"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "تاریخچه کلمه عبور"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -390,13 +390,13 @@
"message": "Suosikki"
},
"unfavorite": {
"message": "Unfavorite"
"message": "Poista suosikeista"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
"message": "Kohde lisättiin suosikkeihin"
},
"itemRemovedFromFavorites": {
"message": "Item removed from favorites"
"message": "Kohde poistettiin suosikeista"
},
"notes": {
"message": "Merkinnät"
@ -420,7 +420,7 @@
"message": "Avaa"
},
"launchWebsite": {
"message": "Launch website"
"message": "Avaa verkkosivusto"
},
"website": {
"message": "Verkkosivusto"
@ -778,7 +778,7 @@
"message": "Avaa"
},
"additionalOptions": {
"message": "Lisäasetukset"
"message": "Lisävalinnat"
},
"enableContextMenuItem": {
"message": "Näytä sisältövalikon valinnat"
@ -845,7 +845,7 @@
"message": "Viennin tyyppi"
},
"accountRestricted": {
"message": "Rajoitettu tilille"
"message": "Tiliä on rajoitettu"
},
"filePasswordAndConfirmFilePasswordDoNotMatch": {
"message": "\"Tiedoston salasana\" ja \"Vahvista tiedoston salasana\" eivät täsmää."
@ -1120,22 +1120,22 @@
"message": "Itse ylläpidetty palvelinympäristö"
},
"selfHostedEnvironmentFooter": {
"message": "Määritä omassa palvelinympäristössäsi suoritettavan Bitwarden-asennuksen pääverkkotunnus."
"message": "Määritä omassa palvelinympäristössäsi suoritettavan Bitwarden-asennuksen perusosoite."
},
"selfHostedBaseUrlHint": {
"message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com"
"message": "Määritä itse ylläpitämäsi Bitwarden-asennuksen perusosoite. Esimerkki: https://bitwarden.yritys.fi."
},
"selfHostedCustomEnvHeader": {
"message": "For advanced configuration, you can specify the base URL of each service independently."
"message": "Edistynyttä määritystä varten voit syöttää jokaisen palvelun perusosoitteen erikseen."
},
"selfHostedEnvFormInvalid": {
"message": "You must add either the base Server URL or at least one custom environment."
"message": "Sinun on lisättävä joko palvelimen perusosoite tai ainakin yksi mukautettu palvelinympäristö."
},
"customEnvironment": {
"message": "Mukautettu palvelinympäristö"
},
"customEnvironmentFooter": {
"message": "Edistyneille käyttäjille. Voit määrittää jokaiselle palvelulle oman pääverkkotunnuksen."
"message": "Edistyneille käyttäjille. Voit määrittää jokaiselle palvelulle oman perusosoitteen."
},
"baseUrl": {
"message": "Palvelimen URL"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Henkilöllisyys"
},
"newItemHeader": {
"message": "Uusi $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Muokkaa $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Salasanahistoria"
},
@ -1444,7 +1462,7 @@
"message": "Kokoelmat"
},
"nCollections": {
"message": "$COUNT$ collections",
"message": "$COUNT$ kokoelmaa",
"placeholders": {
"count": {
"content": "$1",
@ -1682,7 +1700,7 @@
"message": "Automaattitäytä ja tallenna"
},
"fillAndSave": {
"message": "Fill and save"
"message": "Täytä ja tallenna"
},
"autoFillSuccessAndSavedUri": {
"message": "Kohde täytettiin automaattisesti ja URI tallennettiin"
@ -1781,10 +1799,10 @@
"message": "Ok"
},
"errorRefreshingAccessToken": {
"message": "Access Token Refresh Error"
"message": "Käyttötunnisteen päivitysvirhe"
},
"errorRefreshingAccessTokenDesc": {
"message": "No refresh token or API keys found. Please try logging out and logging back in."
"message": "Päivitystunnistetta tai API-avaimia ei löytynyt. Kokeile kirjautua ulos ja takaisin sisään."
},
"desktopSyncVerificationTitle": {
"message": "Työpöytäsynkronoinnin vahvistus"
@ -2344,7 +2362,7 @@
}
},
"forwaderInvalidToken": {
"message": "Virheellinen $SERVICENAME$ -rajapinnan tunniste",
"message": "Virheellinen $SERVICENAME$ API -tunniste",
"description": "Displayed when the user's API token is empty or rejected by the forwarding service.",
"placeholders": {
"servicename": {
@ -2354,7 +2372,7 @@
}
},
"forwaderInvalidTokenWithMessage": {
"message": "Virheellinen $SERVICENAME$ -rajapinnan tunniste: $ERRORMESSAGE$",
"message": "Virheellinen $SERVICENAME$ API -tunniste: $ERRORMESSAGE$",
"description": "Displayed when the user's API token is rejected by the forwarding service with an error message.",
"placeholders": {
"servicename": {
@ -2368,7 +2386,7 @@
}
},
"forwarderNoAccountId": {
"message": "$SERVICENAME$ -palvelun peittämän sähköpostitilin tunnistetta ei saatu.",
"message": "$SERVICENAME$ -palvelun peittämän sähköpostitilin tunnusta ei saatu.",
"description": "Displayed when the forwarding service fails to return an account ID.",
"placeholders": {
"servicename": {
@ -3305,18 +3323,8 @@
"clearFiltersOrTryAnother": {
"message": "Tyhjennä suodattimet tai kokeile toista hakutermiä"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"message": "Kopioi tietoja - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
@ -3325,18 +3333,8 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"message": "Kopioi muistio - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
@ -3346,7 +3344,7 @@
}
},
"moreOptionsLabel": {
"message": "More options, $ITEMNAME$",
"message": "Lisää valintoja, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with more options for an item.",
"placeholders": {
"itemname": {
@ -3356,7 +3354,7 @@
}
},
"moreOptionsTitle": {
"message": "More options - $ITEMNAME$",
"message": "Lisää valintoja - $ITEMNAME$",
"description": "Title for a button that opens a menu with more options for an item.",
"placeholders": {
"itemname": {
@ -3366,7 +3364,7 @@
}
},
"viewItemTitle": {
"message": "View item - $ITEMNAME$",
"message": "Tarkastele kohdetta - $ITEMNAME$",
"description": "Title for a link that opens a view for an item.",
"placeholders": {
"itemname": {
@ -3375,17 +3373,30 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
"message": "Määritä kokoelmat"
},
"copyEmail": {
"message": "Copy email"
"message": "Kopioi sähköpostiosoite"
},
"copyPhone": {
"message": "Copy phone"
"message": "Kopioi puhelinnumero"
},
"copyAddress": {
"message": "Copy address"
"message": "Kopioi osoite"
},
"adminConsole": {
"message": "Hallintapaneelista"
@ -3439,7 +3450,7 @@
}
},
"itemsWithNoFolder": {
"message": "Kohteet, joilla ei ole kansioita"
"message": "Kansiottomat kohteet"
},
"organizationIsDeactivated": {
"message": "Organisaatio on poistettu käytöstä"

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Pagkakakilanlan"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Kasaysayan ng Password"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identité"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Historique des mots de passe"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Effacer les filtres ou essayer un autre terme de recherche"
},
"copyInfoLabel": {
"message": "Copier les informations, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copier les informations - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copier la note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copier la note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assigner une collection"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identidade"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Historial de contrasinais"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -393,10 +393,10 @@
"message": "Unfavorite"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
"message": "פריט נוסף למועדפים"
},
"itemRemovedFromFavorites": {
"message": "Item removed from favorites"
"message": "פריט הוסר מהמועדפים"
},
"notes": {
"message": "הערות"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "זהות"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "היסטוריית סיסמאות"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "पहचान"
},
"newItemHeader": {
"message": "नया $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "$TYPE$ संपादन",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "पासवर्ड इतिहास"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identitet"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Povijest"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -393,10 +393,10 @@
"message": "Nem kedvenc"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
"message": "Az elem bekerült a kedvencekhez."
},
"itemRemovedFromFavorites": {
"message": "Item removed from favorites"
"message": "Az elem kikerült a kedvencekből."
},
"notes": {
"message": "Jegyzetek"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Személyazonosság"
},
"newItemHeader": {
"message": "Új $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "$TYPE$ szerkesztése",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Jelszó előzmények"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Töröljük a szűrőket vagy próbálkozzunk másik keresési kifejezéssel."
},
"copyInfoLabel": {
"message": "Infó másolása, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Infó másolása - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Jegyzet másolása, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Jegyzet másolása - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Automatikus kitöltés - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "Nincsenek másolandó értékek."
},
"assignCollections": {
"message": "Gyűjtemények hozzárendelése"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identitas"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Riwayat Kata Sandi"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identità"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Cronologia delle password"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Cancella i filtri o prova un altro termine di ricerca"
},
"copyInfoLabel": {
"message": "Copia informazioni, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copia informazioni - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copia nota, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copia nota - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assegna raccolte"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "ID"
},
"newItemHeader": {
"message": "$TYPE$ を新規作成",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "$TYPE$ を編集",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "パスワードの履歴"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "フィルタをクリアするか、別の検索ワードをお試しください"
},
"copyInfoLabel": {
"message": "$ITEMNAME$ の情報をコピー",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "情報をコピー - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "$ITEMNAME$ のメモをコピー",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "メモをコピー - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "自動入力 - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "コピーする値がありません"
},
"assignCollections": {
"message": "コレクションを割り当て"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identity"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Password history"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identity"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Password history"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "ಗುರುತಿಸುವಿಕೆ"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "ಪಾಸ್ವರ್ಡ್ ಇತಿಹಾಸ"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -173,16 +173,16 @@
"message": "웹 앱에서 계속하시겠용?"
},
"continueToWebAppDesc": {
"message": "Explore more features of your Bitwarden account on the web app."
"message": "웹 앱에서 Bitwarden 계정의 더 많은 기능을 탐색해보세요."
},
"continueToHelpCenter": {
"message": "Continue to Help Center?"
"message": "도움말 센터로 이동"
},
"continueToHelpCenterDesc": {
"message": "Learn more about how to use Bitwarden on the Help Center."
"message": "Bitwarden의 자세한 사용법은 도움말 센터에서 확인하세요."
},
"continueToBrowserExtensionStore": {
"message": "Continue to browser extension store?"
"message": "브라우저 확장 스토어로 이동하시겠습니까?"
},
"continueToBrowserExtensionStoreDesc": {
"message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now."
@ -205,7 +205,7 @@
"message": "로그아웃"
},
"aboutBitwarden": {
"message": "About Bitwarden"
"message": "Bitwarden 에 대하여"
},
"about": {
"message": "정보"
@ -390,7 +390,7 @@
"message": "즐겨찾기"
},
"unfavorite": {
"message": "Unfavorite"
"message": "즐겨찾기 해제"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
@ -420,7 +420,7 @@
"message": "열기"
},
"launchWebsite": {
"message": "Launch website"
"message": "웹사이트 열기"
},
"website": {
"message": "웹 사이트"
@ -435,7 +435,7 @@
"message": "기타"
},
"unlockMethods": {
"message": "Unlock options"
"message": "잠금 해제 옵션"
},
"unlockMethodNeededToChangeTimeoutActionDesc": {
"message": "잠금 해제 방법을 설정하여 보관함의 시간 초과 동작을 변경하세요."
@ -444,10 +444,10 @@
"message": "설정에서 잠금 해제 수단 설정하기"
},
"sessionTimeoutHeader": {
"message": "Session timeout"
"message": "세션 만료"
},
"otherOptions": {
"message": "Other options"
"message": "기타 옵션"
},
"rateExtension": {
"message": "확장 프로그램 평가"
@ -722,7 +722,7 @@
"message": "\"로그인 추가 알림\"을 사용하면 새 로그인을 사용할 때마다 보관함에 그 로그인을 추가할 것인지 물어봅니다."
},
"addLoginNotificationDescAlt": {
"message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts."
"message": "보관함에 항목이 없을 경우 추가하라는 메시지를 표시합니다. 모든 로그인된 계정에 적용됩니다."
},
"showCardsCurrentTab": {
"message": "탭 페이지에 카드 표시"
@ -754,10 +754,10 @@
"message": "현재 로그인으로 업데이트할 건지 묻기"
},
"changedPasswordNotificationDesc": {
"message": "Ask to update a login's password when a change is detected on a website."
"message": "웹사이트에서 변경 사항이 감지되면 로그인 비밀번호를 업데이트하라는 메시지를 표시합니다."
},
"changedPasswordNotificationDescAlt": {
"message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts."
"message": "웹사이트에서 변경 사항이 감지되면 로그인 비밀번호를 업데이트하라는 메시지를 표시합니다. 모든 로그인된 계정에 적용됩니다."
},
"enableUsePasskeys": {
"message": "패스키를 저장 및 사용할지 묻기"
@ -778,7 +778,7 @@
"message": "잠금 해제"
},
"additionalOptions": {
"message": "Additional options"
"message": "추가 옵션"
},
"enableContextMenuItem": {
"message": "Show context menu options"
@ -803,7 +803,7 @@
"message": "애플리케이션의 색상 테마를 변경합니다."
},
"themeDescAlt": {
"message": "Change the application's color theme. Applies to all logged in accounts."
"message": "애플리케이션 색상 테마를 변경합니다. 모든 로그인된 계정에 적용됩니다."
},
"dark": {
"message": "어두운 테마",
@ -830,7 +830,7 @@
"message": "This file export will be password protected and require the file password to decrypt."
},
"filePassword": {
"message": "File password"
"message": "파일 비밀번호"
},
"exportPasswordDescription": {
"message": "This password will be used to export and import this file"
@ -842,10 +842,10 @@
"message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption."
},
"exportTypeHeading": {
"message": "Export type"
"message": "내보내기 유형"
},
"accountRestricted": {
"message": "Account restricted"
"message": "계정 제한됨"
},
"filePasswordAndConfirmFilePasswordDoNotMatch": {
"message": "“File password” and “Confirm file password“ do not match."
@ -1159,28 +1159,28 @@
"message": "환경 URL 값을 저장했습니다."
},
"showAutoFillMenuOnFormFields": {
"message": "Show auto-fill menu on form fields",
"message": "입력 필드에 자동 완성 메뉴 표시",
"description": "Represents the message for allowing the user to enable the auto-fill overlay"
},
"showAutoFillMenuOnFormFieldsDescAlt": {
"message": "Applies to all logged in accounts."
"message": "모든 로그인된 계정에 적용됩니다."
},
"turnOffBrowserBuiltInPasswordManagerSettings": {
"message": "Turn off your browsers built in password manager settings to avoid conflicts."
"message": "충돌을 방지하기 위해 브라우저의 기본 암호 관리 설정을 해제합니다."
},
"turnOffBrowserBuiltInPasswordManagerSettingsLink": {
"message": "Edit browser settings."
},
"autofillOverlayVisibilityOff": {
"message": "Off",
"message": "끄기",
"description": "Overlay setting select option for disabling autofill overlay"
},
"autofillOverlayVisibilityOnFieldFocus": {
"message": "When field is selected (on focus)",
"message": "필드가 선택되었을 때 (포커스 상태)",
"description": "Overlay appearance select option for showing the field on focus of the input element"
},
"autofillOverlayVisibilityOnButtonClick": {
"message": "When auto-fill icon is selected",
"message": "자동 완성 아이콘이 선택되었을 때",
"description": "Overlay appearance select option for showing the field on click of the overlay icon"
},
"enableAutoFillOnPageLoad": {
@ -1190,7 +1190,7 @@
"message": "로그인 양식을 감지하면 웹 페이지 로드 시 자동 완성을 자동으로 수행합니다."
},
"experimentalFeature": {
"message": "Compromised or untrusted websites can exploit auto-fill on page load."
"message": "취약하거나 신뢰할 수 없는 웹사이트 페이지 로드 시 자동 완성이 악용될 수 있습니다."
},
"learnMoreAboutAutofill": {
"message": "Learn more about auto-fill"
@ -1273,13 +1273,13 @@
"message": "Show a recognizable image next to each login."
},
"faviconDescAlt": {
"message": "Show a recognizable image next to each login. Applies to all logged in accounts."
"message": "각 로그인 정보 옆에 인식할 수 있는 이미지를 표시합니다. 모든 로그인된 계정에 적용됩니다."
},
"enableBadgeCounter": {
"message": "Show badge counter"
"message": "배지 갯수 표시"
},
"badgeCounterDesc": {
"message": "Indicate how many logins you have for the current web page."
"message": "현재 웹 페이지에 저장된 로그인 정보의 수를 표시합니다."
},
"cardholderName": {
"message": "카드 소유자 이름"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "신원"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "비밀번호 변경 기록"
},
@ -1557,7 +1575,7 @@
"description": "ex. Date this item was updated"
},
"dateCreated": {
"message": "Created",
"message": "생성됨",
"description": "ex. Date this item was created"
},
"datePasswordUpdated": {
@ -1614,7 +1632,7 @@
"message": "잘못된 PIN 코드입니다."
},
"tooManyInvalidPinEntryAttemptsLoggingOut": {
"message": "Too many invalid PIN entry attempts. Logging out."
"message": "잘못된 PIN 입력 시도가 너무 많습니다. 로그아웃 합니다."
},
"unlockWithBiometrics": {
"message": "생체 인식을 사용하여 잠금 해제"
@ -1712,13 +1730,13 @@
"message": "마스터 비밀번호 설정"
},
"currentMasterPass": {
"message": "Current master password"
"message": "현재 마스터 비밀번호"
},
"newMasterPass": {
"message": "New master password"
"message": "새 마스터 비밀번호"
},
"confirmNewMasterPass": {
"message": "Confirm new master password"
"message": "새 마스터 비밀번호 확인"
},
"masterPasswordPolicyInEffect": {
"message": "하나 이상의 단체 정책이 마스터 비밀번호가 다음 사항을 따르도록 요구합니다:"
@ -2137,7 +2155,7 @@
"message": "폴더 선택..."
},
"noFoldersFound": {
"message": "No folders found",
"message": "폴더를 찾을 수 없습니다.",
"description": "Used as a message within the notification bar when no folders are found"
},
"orgPermissionsUpdatedMustSetPassword": {
@ -2149,7 +2167,7 @@
"description": "Used as a card title description on the set password page to explain why the user is there"
},
"verificationRequired": {
"message": "Verification required",
"message": "인증 필요",
"description": "Default title for the user verification dialog."
},
"hours": {
@ -2310,7 +2328,7 @@
"message": "서비스"
},
"forwardedEmail": {
"message": "Forwarded email alias"
"message": "포워딩된 이메일 별칭"
},
"forwardedEmailDesc": {
"message": "Generate an email alias with an external forwarding service."
@ -2418,7 +2436,7 @@
}
},
"hostname": {
"message": "Hostname",
"message": "호스트 이름",
"description": "Part of a URL."
},
"apiAccessToken": {
@ -2431,7 +2449,7 @@
"message": "키 커넥터 오류: 키 커넥터가 사용 가능한지 및 정상적으로 작동하고 있는지 확인해주세요."
},
"premiumSubcriptionRequired": {
"message": "Premium subscription required"
"message": "프리미엄 구독이 필요합니다"
},
"organizationIsDisabled": {
"message": "Organization suspended."
@ -2458,13 +2476,13 @@
"message": "to reset to pre-configured settings"
},
"serverVersion": {
"message": "Server version"
"message": "서버 버전"
},
"selfHostedServer": {
"message": "self-hosted"
"message": "자체 호스팅"
},
"thirdParty": {
"message": "Third-party"
"message": "제 3자"
},
"thirdPartyServerMessage": {
"message": "Connected to third-party server implementation, $SERVERNAME$. Please verify bugs using the official server, or report them to the third-party server.",
@ -2491,13 +2509,13 @@
"message": "Logging in as"
},
"notYou": {
"message": "Not you?"
"message": "본인이 아닌가요?"
},
"newAroundHere": {
"message": "New around here?"
"message": "새로 찾아오셨나요?"
},
"rememberEmail": {
"message": "Remember email"
"message": "이메일 기억하기"
},
"loginWithDevice": {
"message": "Log in with device"
@ -2575,13 +2593,13 @@
"message": "Got it"
},
"autofillSettings": {
"message": "Auto-fill settings"
"message": "자동 완성 설정"
},
"autofillShortcut": {
"message": "Auto-fill keyboard shortcut"
"message": "자동 완성 키보드 단축키"
},
"autofillShortcutNotSet": {
"message": "The auto-fill shortcut is not set. Change this in the browser's settings."
"message": "자동 완성 단축키가 설정되지 않았습니다. 브라우저 설정에서 단축키를 변경하세요."
},
"autofillShortcutText": {
"message": "The auto-fill shortcut is: $COMMAND$. Change this in the browser's settings.",
@ -2611,7 +2629,7 @@
"message": "Device approval required. Select an approval option below:"
},
"rememberThisDevice": {
"message": "Remember this device"
"message": "이 기기 기억하기"
},
"uncheckIfPublicDevice": {
"message": "Uncheck if using a public device"
@ -2620,7 +2638,7 @@
"message": "Approve from your other device"
},
"requestAdminApproval": {
"message": "Request admin approval"
"message": "관리자 승인 필요"
},
"approveWithMasterPassword": {
"message": "Approve with master password"
@ -2657,16 +2675,16 @@
"message": "Access denied. You do not have permission to view this page."
},
"general": {
"message": "General"
"message": "일반"
},
"display": {
"message": "Display"
"message": "화면"
},
"accountSuccessfullyCreated": {
"message": "Account successfully created!"
"message": "계정이 생성되었습니다!"
},
"adminApprovalRequested": {
"message": "Admin approval requested"
"message": "관리자 승인 필요"
},
"adminApprovalRequestSentToAdmins": {
"message": "Your request has been sent to your admin."
@ -2693,7 +2711,7 @@
"message": "required"
},
"search": {
"message": "Search"
"message": "검색"
},
"inputMinLength": {
"message": "Input must be at least $COUNT$ characters long.",
@ -2741,7 +2759,7 @@
}
},
"multipleInputEmails": {
"message": "1 or more emails are invalid"
"message": "하나 이상의 이메일이 유효하지 않습니다."
},
"inputTrimValidator": {
"message": "Input must not contain only whitespace.",
@ -2811,7 +2829,7 @@
"description": "Notification message for when an import is in progress."
},
"dataSuccessfullyImported": {
"message": "Data successfully imported!",
"message": "데이터 가져오기 성공!",
"description": "Notification message for when an import has completed successfully."
},
"dataImportFailed": {
@ -2857,7 +2875,7 @@
"description": "Text to display in overlay when the account is locked."
},
"unlockAccount": {
"message": "Unlock account",
"message": "계정 잠금 해제",
"description": "Button text to display in overlay when the account is locked."
},
"fillCredentialsFor": {
@ -2873,7 +2891,7 @@
"description": "Text to show in overlay if there are no matching items"
},
"newItem": {
"message": "New item",
"message": "새 항목",
"description": "Button text to display in overlay when there are no matching items"
},
"addNewVaultItem": {
@ -2919,7 +2937,7 @@
}
},
"tryAgain": {
"message": "Try again"
"message": "다시 시도"
},
"verificationRequiredForActionSetPinToContinue": {
"message": "Verification required for this action. Set a PIN to continue."
@ -2979,13 +2997,13 @@
"message": "Popout extension"
},
"launchDuo": {
"message": "Launch Duo"
"message": "Duo 실행"
},
"importFormatError": {
"message": "Data is not formatted correctly. Please check your import file and try again."
"message": "데이터의 포맷이 올바르지 않습니다. 불러올 파일을 확인하고 다시 시도해 주십시오."
},
"importNothingError": {
"message": "Nothing was imported."
"message": "아무것도 가져오지 못했습니다."
},
"importEncKeyError": {
"message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data."
@ -3232,7 +3250,7 @@
"description": "Label indicating the most common import formats"
},
"overrideDefaultBrowserAutofillTitle": {
"message": "Make Bitwarden your default password manager?",
"message": "Bitwarden을 기본 비밀번호 관리자로 지정하시겠습니까?",
"description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior"
},
"overrideDefaultBrowserAutofillDescription": {
@ -3240,7 +3258,7 @@
"description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior"
},
"overrideDefaultBrowserAutoFillSettings": {
"message": "Make Bitwarden your default password manager",
"message": "Bitwarden을 기본 비밀번호 관리자로 지정",
"description": "Label for the setting that allows overriding the default browser autofill settings"
},
"privacyPermissionAdditionNotGrantedTitle": {
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},
@ -3391,13 +3402,13 @@
"message": "Admin Console"
},
"accountSecurity": {
"message": "Account security"
"message": "계정 보안"
},
"notifications": {
"message": "Notifications"
"message": "알림"
},
"appearance": {
"message": "Appearance"
"message": "화면 스타일"
},
"errorAssigningTargetCollection": {
"message": "Error assigning target collection."

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Tapatybė"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Slaptažodžio istorija"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -390,13 +390,13 @@
"message": "Izlasē"
},
"unfavorite": {
"message": "Unfavorite"
"message": "Noņemt no izlases"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
"message": "Vienums pievienots izlasē"
},
"itemRemovedFromFavorites": {
"message": "Item removed from favorites"
"message": "Vienums noņemts no izlases"
},
"notes": {
"message": "Piezīmes"
@ -420,7 +420,7 @@
"message": "Palaist"
},
"launchWebsite": {
"message": "Launch website"
"message": "Atvērt tīmekļvietni"
},
"website": {
"message": "Tīmekļa vietne"
@ -612,7 +612,7 @@
"message": "Atteicies"
},
"loggedOutDesc": {
"message": "You have been logged out of your account."
"message": "Notika izrakstīšanās no Tava konta."
},
"loginExpired": {
"message": "Pieteikšanās sesija ir beigusies."
@ -1123,13 +1123,13 @@
"message": "Norādīt pašuzstādīta Bitwarden pamata URL."
},
"selfHostedBaseUrlHint": {
"message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com"
"message": "Jānorāda sava pašizvietotā Bitward servera pamata URL. Piemērs: https://bitwarden.uznemums.lv"
},
"selfHostedCustomEnvHeader": {
"message": "For advanced configuration, you can specify the base URL of each service independently."
"message": "Papildu konfigurācijā ir iespējams norādīt URL katram pakalpojumam atsevišķi."
},
"selfHostedEnvFormInvalid": {
"message": "You must add either the base Server URL or at least one custom environment."
"message": "Jāpievieno vai no servera pamata URL vai vismaz viena pielāgota vide."
},
"customEnvironment": {
"message": "Pielāgota vide"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identitāte"
},
"newItemHeader": {
"message": "Jauns/a $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Labot $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Paroļu vēsture"
},
@ -1444,7 +1462,7 @@
"message": "Krājumi"
},
"nCollections": {
"message": "$COUNT$ collections",
"message": "$COUNT$ krājumi",
"placeholders": {
"count": {
"content": "$1",
@ -1682,7 +1700,7 @@
"message": "Automātiski aizpildīt un saglabāt"
},
"fillAndSave": {
"message": "Fill and save"
"message": "Aizpildīt un saglabāt"
},
"autoFillSuccessAndSavedUri": {
"message": "Automātiski aizpildīts vienums un saglabāts URI"
@ -1781,10 +1799,10 @@
"message": "Labi"
},
"errorRefreshingAccessToken": {
"message": "Access Token Refresh Error"
"message": "Piekļuves pilnvaras atsvaizināšanas kļūda"
},
"errorRefreshingAccessTokenDesc": {
"message": "No refresh token or API keys found. Please try logging out and logging back in."
"message": "Netika atrastas atsvaidzināšanas pilnvaras vai API atslēgas. Lūgums mēģināt izrakstīties un atkal pieteikties."
},
"desktopSyncVerificationTitle": {
"message": "Darbvirsmas sinhronizācijas apstiprinājums"
@ -3305,18 +3323,8 @@
"clearFiltersOrTryAnother": {
"message": "Jānotīra atlases vērtības vai jāmēģina cits meklēšanas vaicājums"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"message": "Ievietot starpliktuvē informāciju - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
@ -3325,18 +3333,8 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"message": "Ievietot starpliktuvē piezīmi - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
@ -3346,7 +3344,7 @@
}
},
"moreOptionsLabel": {
"message": "More options, $ITEMNAME$",
"message": "Vairāk iespēju, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with more options for an item.",
"placeholders": {
"itemname": {
@ -3356,7 +3354,7 @@
}
},
"moreOptionsTitle": {
"message": "More options - $ITEMNAME$",
"message": "Vairāk iespēju - $ITEMNAME$",
"description": "Title for a button that opens a menu with more options for an item.",
"placeholders": {
"itemname": {
@ -3366,7 +3364,7 @@
}
},
"viewItemTitle": {
"message": "View item - $ITEMNAME$",
"message": "Skatīt vienumu - $ITEMNAME$",
"description": "Title for a link that opens a view for an item.",
"placeholders": {
"itemname": {
@ -3375,17 +3373,30 @@
}
}
},
"autofillTitle": {
"message": "Automātiski aizpildīt - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "Nav vērtību, ko ievietot starpliktuvē"
},
"assignCollections": {
"message": "Assign collections"
"message": "Piešķirt krājumus"
},
"copyEmail": {
"message": "Copy email"
"message": "Ievietot starpliktuvē e-pasta adresi"
},
"copyPhone": {
"message": "Copy phone"
"message": "Ievietot starpliktuvē tālruņa numuru"
},
"copyAddress": {
"message": "Copy address"
"message": "Ievietot starpliktuvē adresi"
},
"adminConsole": {
"message": "pārvaldības konsolē,"
@ -3439,12 +3450,12 @@
}
},
"itemsWithNoFolder": {
"message": "Items with no folder"
"message": "Vienumi bez mapes"
},
"organizationIsDeactivated": {
"message": "Organization is deactivated"
"message": "Apvienība ir atspējota"
},
"contactYourOrgAdmin": {
"message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance."
"message": "Atspējotu apvienību vienumiem nevar piekļūt. Jāsazinās ar apvienības īpašnieku, lai iegūtu palīdzību."
}
}

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "ഐഡന്റിറ്റി"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "പാസ്സ്‌വേഡ് നാൾവഴി"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identity"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Password history"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identity"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Password history"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identitet"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Passordhistorikk"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identity"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Password history"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identiteit"
},
"newItemHeader": {
"message": "Nieuw $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "$TYPE$ bewerken",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Geschiedenis"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Wis filters of probeer een andere zoekterm"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Automatisch invullen - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "Geen waarden om te kopiëren"
},
"assignCollections": {
"message": "Collecties toewijzen"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identity"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Password history"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identity"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Password history"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Tożsamość"
},
"newItemHeader": {
"message": "Nowy $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edytuj $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Historia hasła"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Wyczyść filtry lub użyj innej frazy"
},
"copyInfoLabel": {
"message": "Skopiuj informacje, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Skopiuj informacje - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Skopiuj notatkę, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Skopiuj notatkę - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Autouzupełnij - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "Brak wartości do skopiowania"
},
"assignCollections": {
"message": "Przypisz kolekcje"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identidade"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Histórico de Senha"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Limpar filtros ou tentar outro termo de pesquisa"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identidade"
},
"newItemHeader": {
"message": "Novo(a) $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Editar $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Histórico de palavras-passe"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Limpe os filtros ou tente outro termo de pesquisa"
},
"copyInfoLabel": {
"message": "Copiar informações, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copiar informações - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copiar nota, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copiar nota - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Preencher automaticamente - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "Não há valores a copiar"
},
"assignCollections": {
"message": "Atribuir coleções"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identitate"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Istoric parole"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -393,10 +393,10 @@
"message": "Удалить из избранного"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
"message": "Элемент добавлен в избранное"
},
"itemRemovedFromFavorites": {
"message": "Item removed from favorites"
"message": "Элемент удален из избранного"
},
"notes": {
"message": "Заметки"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Личная информация"
},
"newItemHeader": {
"message": "Новый $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Изменить $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "История паролей"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Очистите фильтры или попробуйте другой поисковый запрос"
},
"copyInfoLabel": {
"message": "Скопировать информацию, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Скопировать информацию - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Скопировать заметку, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Скопировать заметку - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Автозаполнение - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "Нет значений для копирования"
},
"assignCollections": {
"message": "Назначить коллекции"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "අනන්යතාවය"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "මුරපද ඉතිහාසය"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -393,10 +393,10 @@
"message": "Odstrániť z obľúbených"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
"message": "Položka pridaná medzi obľúbené"
},
"itemRemovedFromFavorites": {
"message": "Item removed from favorites"
"message": "Položka odobraná z obľúbených"
},
"notes": {
"message": "Poznámky"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identita"
},
"newItemHeader": {
"message": "Nové $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Upraviť $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "História hesla"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Vymažte filtre alebo zmeňte vyhľadávaný výraz"
},
"copyInfoLabel": {
"message": "Skopírovať info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Skopírovať info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Skopírovať poznámku, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Skopírovať poznámku - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Automatické vyplnenie - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "Nie je čo kopírovať"
},
"assignCollections": {
"message": "Prideliť zbierky"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identiteta"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Zgodovina gesel"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Идентитет"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Историја Лозинке"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Обришите филтере или покушајте са другим термином"
},
"copyInfoLabel": {
"message": "Копирај информације, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Копирај информације - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Копирај Белешку, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Копирај Белешку - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Додели колекције"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identitet"
},
"newItemHeader": {
"message": "Ny $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Redigera $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Lösenordshistorik"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Tilldela samlingar"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Identity"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Password history"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "ข้อมูลระบุตัวตน"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "ประวัติของรหัสผ่าน"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Kimlik"
},
"newItemHeader": {
"message": "Yeni $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Parola geçmişi"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Filtreleri temizleyin veya başka bir arama yapmayı deneyin"
},
"copyInfoLabel": {
"message": "Bilgileri kopyala, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Bilgileri kopyala - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Notu kopyala, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Notu kopyala - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Otomatik doldur - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "Kopyalanacak değer yok"
},
"assignCollections": {
"message": "Koleksiyon ata"
},

View File

@ -393,10 +393,10 @@
"message": "Вилучити з обраного"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
"message": "Запис додано до обраного"
},
"itemRemovedFromFavorites": {
"message": "Item removed from favorites"
"message": "Запис вилучено з обраного"
},
"notes": {
"message": "Нотатки"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Особисті дані"
},
"newItemHeader": {
"message": "Новий $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Редагувати $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Історія паролів"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Скиньте фільтри або спробуйте іншу умову пошуку"
},
"copyInfoLabel": {
"message": "Копіювати інформацію, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Копіювати інформацію $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Копіювати нотатку, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Копіювати нотатку $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Автозаповнення $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "Немає значень для копіювання"
},
"assignCollections": {
"message": "Призначити збірки"
},

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "Danh tính"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "Lịch sử mật khẩu"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -390,13 +390,13 @@
"message": "收藏"
},
"unfavorite": {
"message": "Unfavorite"
"message": "取消收藏"
},
"itemAddedToFavorites": {
"message": "Item added to favorites"
"message": "项目已添加到收藏夹"
},
"itemRemovedFromFavorites": {
"message": "Item removed from favorites"
"message": "项目已移出收藏夹"
},
"notes": {
"message": "备注"
@ -420,7 +420,7 @@
"message": "前往"
},
"launchWebsite": {
"message": "Launch website"
"message": "启动网站"
},
"website": {
"message": "网站"
@ -612,7 +612,7 @@
"message": "已注销"
},
"loggedOutDesc": {
"message": "You have been logged out of your account."
"message": "您已注销您的账户。"
},
"loginExpired": {
"message": "您的登录会话已过期。"
@ -1123,13 +1123,13 @@
"message": "指定您本地托管的 Bitwarden 安装的基础 URL。"
},
"selfHostedBaseUrlHint": {
"message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com"
"message": "指定您的本地托管 Bitwarden 安装的基础 URL。例如https://bitwarden.company.com"
},
"selfHostedCustomEnvHeader": {
"message": "For advanced configuration, you can specify the base URL of each service independently."
"message": "对于高级配置,您可以单独指定每个服务的基础 URL。"
},
"selfHostedEnvFormInvalid": {
"message": "You must add either the base Server URL or at least one custom environment."
"message": "您必须添加基础服务器 URL 或至少添加一个自定义环境。"
},
"customEnvironment": {
"message": "自定义环境"
@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "身份"
},
"newItemHeader": {
"message": "新增 $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "编辑 $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "密码历史记录"
},
@ -1444,7 +1462,7 @@
"message": "集合"
},
"nCollections": {
"message": "$COUNT$ collections",
"message": "$COUNT$ 个集合",
"placeholders": {
"count": {
"content": "$1",
@ -1682,7 +1700,7 @@
"message": "自动填充并保存"
},
"fillAndSave": {
"message": "Fill and save"
"message": "填充并保存"
},
"autoFillSuccessAndSavedUri": {
"message": "项目已自动填充且 URI 已保存"
@ -1781,10 +1799,10 @@
"message": "确定"
},
"errorRefreshingAccessToken": {
"message": "Access Token Refresh Error"
"message": "访问令牌刷新错误"
},
"errorRefreshingAccessTokenDesc": {
"message": "No refresh token or API keys found. Please try logging out and logging back in."
"message": "未找到刷新令牌或 API 密钥。请尝试注销然后重新登录。"
},
"desktopSyncVerificationTitle": {
"message": "桌面同步验证"
@ -2675,7 +2693,7 @@
"message": "批准后,您将收到通知。"
},
"troubleLoggingIn": {
"message": "登录遇到问题"
"message": "登录遇到问题"
},
"loginApproved": {
"message": "登录已批准"
@ -3305,18 +3323,8 @@
"clearFiltersOrTryAnother": {
"message": "清除筛选器或尝试另一个搜索词"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"message": "复制信息 - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
@ -3325,18 +3333,8 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"message": "复制备注 - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
@ -3346,7 +3344,7 @@
}
},
"moreOptionsLabel": {
"message": "More options, $ITEMNAME$",
"message": "更多选项,$ITEMNAME$",
"description": "Aria label for a button that opens a menu with more options for an item.",
"placeholders": {
"itemname": {
@ -3356,7 +3354,7 @@
}
},
"moreOptionsTitle": {
"message": "More options - $ITEMNAME$",
"message": "更多选项 - $ITEMNAME$",
"description": "Title for a button that opens a menu with more options for an item.",
"placeholders": {
"itemname": {
@ -3366,7 +3364,7 @@
}
},
"viewItemTitle": {
"message": "View item - $ITEMNAME$",
"message": "查看项目 - $ITEMNAME$",
"description": "Title for a link that opens a view for an item.",
"placeholders": {
"itemname": {
@ -3375,17 +3373,30 @@
}
}
},
"autofillTitle": {
"message": "自动填充 - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "没有要复制的值"
},
"assignCollections": {
"message": "Assign collections"
"message": "分配集合"
},
"copyEmail": {
"message": "Copy email"
"message": "复制电子邮件地址"
},
"copyPhone": {
"message": "Copy phone"
"message": "复制电话号码"
},
"copyAddress": {
"message": "Copy address"
"message": "复制地址"
},
"adminConsole": {
"message": "管理控制台"
@ -3426,7 +3437,7 @@
}
},
"new": {
"message": "新"
"message": "新"
},
"removeItem": {
"message": "删除 $NAME$",
@ -3439,12 +3450,12 @@
}
},
"itemsWithNoFolder": {
"message": "Items with no folder"
"message": "无文件夹的项目"
},
"organizationIsDeactivated": {
"message": "组织已停用"
},
"contactYourOrgAdmin": {
"message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance."
"message": "无法访问已停用组织中的项目。请联系您的组织所有者获取协助。"
}
}

View File

@ -1434,6 +1434,24 @@
"typeIdentity": {
"message": "身分"
},
"newItemHeader": {
"message": "New $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"editItemHeader": {
"message": "Edit $TYPE$",
"placeholders": {
"type": {
"content": "$1",
"example": "Login"
}
}
},
"passwordHistory": {
"message": "密碼歷史記錄"
},
@ -3305,16 +3323,6 @@
"clearFiltersOrTryAnother": {
"message": "Clear filters or try another search term"
},
"copyInfoLabel": {
"message": "Copy info, $ITEMNAME$",
"description": "Aria label for a button that opens a menu with options to copy information from an item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"copyInfoTitle": {
"message": "Copy info - $ITEMNAME$",
"description": "Title for a button that opens a menu with options to copy information from an item.",
@ -3325,16 +3333,6 @@
}
}
},
"copyNoteLabel": {
"message": "Copy Note, $ITEMNAME$",
"description": "Aria label for a button copies a note to the clipboard.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Note Item"
}
}
},
"copyNoteTitle": {
"message": "Copy Note - $ITEMNAME$",
"description": "Title for a button copies a note to the clipboard.",
@ -3375,6 +3373,19 @@
}
}
},
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that auto-fills a login item.",
"placeholders": {
"itemname": {
"content": "$1",
"example": "Secret Item"
}
}
},
"noValuesToCopy": {
"message": "No values to copy"
},
"assignCollections": {
"message": "Assign collections"
},

View File

@ -30,7 +30,9 @@
</form>
<p class="createAccountLink">
{{ "newAroundHere" | i18n }}
<a routerLink="/register" (click)="setLoginEmailValues()">{{ "createAccount" | i18n }}</a>
<a [routerLink]="registerRoute" (click)="setLoginEmailValues()">{{
"createAccount" | i18n
}}</a>
</p>
</div>
</div>

View File

@ -5,6 +5,8 @@ import { Subject, firstValueFrom, takeUntil } from "rxjs";
import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component";
import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -26,6 +28,9 @@ export class HomeComponent implements OnInit, OnDestroy {
rememberEmail: [false],
});
// TODO: remove when email verification flag is removed
registerRoute = "/register";
constructor(
protected platformUtilsService: PlatformUtilsService,
private formBuilder: FormBuilder,
@ -34,9 +39,19 @@ export class HomeComponent implements OnInit, OnDestroy {
private environmentService: EnvironmentService,
private loginEmailService: LoginEmailServiceAbstraction,
private accountSwitcherService: AccountSwitcherService,
private configService: ConfigService,
) {}
async ngOnInit(): Promise<void> {
// TODO: remove when email verification flag is removed
const emailVerification = await this.configService.getFeatureFlag(
FeatureFlag.EmailVerification,
);
if (emailVerification) {
this.registerRoute = "/signup";
}
const email = this.loginEmailService.getEmail();
const rememberEmail = this.loginEmailService.getRememberEmail();

View File

@ -1,10 +1,13 @@
<div id="login-initiated">
<header>
<h1 class="margin-auto">
<app-header>
<div class="left">
<app-pop-out></app-pop-out>
</div>
<h1 class="center">
<span class="title">{{ "loginInitiated" | i18n }}</span>
</h1>
</header>
<div class="right"></div>
</app-header>
<div class="content login-page">
<div class="full-loading-spinner" *ngIf="loading">
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>

View File

@ -13,6 +13,7 @@ import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstraction
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -51,6 +52,7 @@ export class LoginComponent extends BaseLoginComponent {
loginEmailService: LoginEmailServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction,
webAuthnLoginService: WebAuthnLoginServiceAbstraction,
configService: ConfigService,
) {
super(
devicesApiService,
@ -71,6 +73,7 @@ export class LoginComponent extends BaseLoginComponent {
loginEmailService,
ssoLoginService,
webAuthnLoginService,
configService,
);
super.onSuccessfulLogin = async () => {
await syncService.fullSync(true);

View File

@ -1,12 +1,16 @@
import { AutofillInlineMenuContentService } from "../overlay/inline-menu/content/autofill-inline-menu-content.service";
import { AutofillOverlayContentService } from "../services/autofill-overlay-content.service";
import { InlineMenuFieldQualificationService } from "../services/inline-menu-field-qualification.service";
import { setupAutofillInitDisconnectAction } from "../utils";
import AutofillInit from "./autofill-init";
(function (windowContext) {
if (!windowContext.bitwardenAutofillInit) {
const autofillOverlayContentService = new AutofillOverlayContentService();
const inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
const autofillOverlayContentService = new AutofillOverlayContentService(
inlineMenuFieldQualificationService,
);
let inlineMenuElements: AutofillInlineMenuContentService;
if (globalThis.self === globalThis.top) {
inlineMenuElements = new AutofillInlineMenuContentService();

View File

@ -3,6 +3,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio
import { SubFrameOffsetData } from "../../background/abstractions/overlay.background";
import { AutofillExtensionMessageParam } from "../../content/abstractions/autofill-init";
import AutofillField from "../../models/autofill-field";
import AutofillPageDetails from "../../models/autofill-page-details";
import { ElementWithOpId, FormFieldElement } from "../../types";
export type OpenAutofillInlineMenuOptions = {
@ -36,9 +37,10 @@ export interface AutofillOverlayContentService {
pageDetailsUpdateRequired: boolean;
messageHandlers: AutofillOverlayContentExtensionMessageHandlers;
init(): void;
setupInlineMenuListenerOnField(
setupInlineMenu(
autofillFieldElement: ElementWithOpId<FormFieldElement>,
autofillFieldData: AutofillField,
pageDetails: AutofillPageDetails,
): Promise<void>;
blurMostRecentlyFocusedField(isClosingInlineMenu?: boolean): void;
destroy(): void;

View File

@ -0,0 +1,6 @@
import AutofillField from "../../models/autofill-field";
import AutofillPageDetails from "../../models/autofill-page-details";
export interface InlineMenuFieldQualificationService {
isFieldForLoginForm(field: AutofillField, pageDetails: AutofillPageDetails): boolean;
}

View File

@ -10,17 +10,21 @@ import {
RedirectFocusDirection,
} from "../enums/autofill-overlay.enum";
import AutofillField from "../models/autofill-field";
import AutofillForm from "../models/autofill-form";
import AutofillPageDetails from "../models/autofill-page-details";
import { createAutofillFieldMock } from "../spec/autofill-mocks";
import { flushPromises, postWindowMessage, sendMockExtensionMessage } from "../spec/testing-utils";
import { ElementWithOpId, FillableFormFieldElement, FormFieldElement } from "../types";
import { AutoFillConstants } from "./autofill-constants";
import { AutofillOverlayContentService } from "./autofill-overlay-content.service";
import { InlineMenuFieldQualificationService } from "./inline-menu-field-qualification.service";
const defaultWindowReadyState = document.readyState;
const defaultDocumentVisibilityState = document.visibilityState;
describe("AutofillOverlayContentService", () => {
let autofillInit: AutofillInit;
let inlineMenuFieldQualificationService: InlineMenuFieldQualificationService;
let autofillOverlayContentService: AutofillOverlayContentService;
let sendExtensionMessageSpy: jest.SpyInstance;
const sendResponseSpy = jest.fn();
@ -35,7 +39,10 @@ describe("AutofillOverlayContentService", () => {
});
beforeEach(() => {
autofillOverlayContentService = new AutofillOverlayContentService();
inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
autofillOverlayContentService = new AutofillOverlayContentService(
inlineMenuFieldQualificationService,
);
autofillInit = new AutofillInit(autofillOverlayContentService);
autofillInit.init();
sendExtensionMessageSpy = jest
@ -120,9 +127,10 @@ describe("AutofillOverlayContentService", () => {
});
});
describe("setupInlineMenuListenerOnField", () => {
describe("setupInlineMenu", () => {
let autofillFieldElement: ElementWithOpId<FormFieldElement>;
let autofillFieldData: AutofillField;
let pageDetailsMock: AutofillPageDetails;
beforeEach(() => {
document.body.innerHTML = `
@ -144,87 +152,52 @@ describe("AutofillOverlayContentService", () => {
placeholder: "username",
elementNumber: 1,
});
const passwordFieldData = createAutofillFieldMock({
opid: "password-field",
form: "validFormId",
elementNumber: 2,
autocompleteType: "current-password",
type: "password",
});
pageDetailsMock = mock<AutofillPageDetails>({
forms: { validFormId: mock<AutofillForm>() },
fields: [autofillFieldData, passwordFieldData],
});
});
describe("skips setup for ignored form fields", () => {
beforeEach(() => {
autofillFieldData = mock<AutofillField>();
});
it("ignores fields that are readonly", async () => {
autofillFieldData.readonly = true;
await autofillOverlayContentService.setupInlineMenuListenerOnField(
autofillFieldElement,
autofillFieldData,
);
expect(autofillFieldElement.addEventListener).not.toHaveBeenCalled();
});
it("ignores fields that contain a disabled attribute", async () => {
autofillFieldData.disabled = true;
await autofillOverlayContentService.setupInlineMenuListenerOnField(
autofillFieldElement,
autofillFieldData,
);
expect(autofillFieldElement.addEventListener).not.toHaveBeenCalled();
});
it("ignores fields that are not viewable", async () => {
autofillFieldData.viewable = false;
await autofillOverlayContentService.setupInlineMenuListenerOnField(
autofillFieldElement,
autofillFieldData,
);
expect(autofillFieldElement.addEventListener).not.toHaveBeenCalled();
autofillFieldData = mock<AutofillField>({
type: "text",
htmlName: "username",
htmlID: "username",
placeholder: "username",
});
});
it("ignores fields that are part of the ExcludedInlineMenuTypes", () => {
AutoFillConstants.ExcludedInlineMenuTypes.forEach(async (excludedType) => {
autofillFieldData.type = excludedType;
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
expect(autofillFieldElement.addEventListener).not.toHaveBeenCalled();
});
});
it("ignores fields that contain the keyword `search`", async () => {
autofillFieldData.placeholder = "search";
await autofillOverlayContentService.setupInlineMenuListenerOnField(
autofillFieldElement,
autofillFieldData,
);
expect(autofillFieldElement.addEventListener).not.toHaveBeenCalled();
});
it("ignores fields that contain the keyword `captcha` ", async () => {
autofillFieldData.placeholder = "captcha";
await autofillOverlayContentService.setupInlineMenuListenerOnField(
autofillFieldElement,
autofillFieldData,
);
expect(autofillFieldElement.addEventListener).not.toHaveBeenCalled();
});
it("ignores fields that do not appear as a login field", async () => {
autofillFieldData.htmlName = "another-type-of-field";
autofillFieldData.htmlID = "another-type-of-field";
autofillFieldData.placeholder = "another-type-of-field";
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
expect(autofillFieldElement.addEventListener).not.toHaveBeenCalled();
@ -234,9 +207,10 @@ describe("AutofillOverlayContentService", () => {
it("skips setup on fields that have been previously set up", async () => {
autofillOverlayContentService["formFieldElements"].add(autofillFieldElement);
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
expect(autofillFieldElement.addEventListener).not.toHaveBeenCalled();
@ -247,9 +221,10 @@ describe("AutofillOverlayContentService", () => {
sendExtensionMessageSpy.mockResolvedValueOnce(undefined);
autofillOverlayContentService["inlineMenuVisibility"] = undefined;
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("getAutofillInlineMenuVisibility");
@ -262,9 +237,10 @@ describe("AutofillOverlayContentService", () => {
sendExtensionMessageSpy.mockResolvedValueOnce(AutofillOverlayVisibility.OnFieldFocus);
autofillOverlayContentService["inlineMenuVisibility"] = undefined;
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
expect(autofillOverlayContentService["inlineMenuVisibility"]).toEqual(
@ -285,9 +261,10 @@ describe("AutofillOverlayContentService", () => {
"op-1-username-field-focus-handler": focusHandler,
};
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
expect(autofillFieldElement.removeEventListener).toHaveBeenNthCalledWith(
@ -314,9 +291,10 @@ describe("AutofillOverlayContentService", () => {
describe("form field blur event listener", () => {
beforeEach(async () => {
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
});
@ -337,9 +315,10 @@ describe("AutofillOverlayContentService", () => {
describe("form field keyup event listener", () => {
beforeEach(async () => {
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
jest.spyOn(globalThis.customElements, "define").mockImplementation();
});
@ -433,9 +412,10 @@ describe("AutofillOverlayContentService", () => {
) as ElementWithOpId<HTMLSpanElement>;
jest.spyOn(autofillOverlayContentService as any, "storeModifiedFormElement");
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
spanAutofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
spanAutofillFieldElement.dispatchEvent(new Event("input"));
@ -447,9 +427,10 @@ describe("AutofillOverlayContentService", () => {
autofillOverlayContentService["mostRecentlyFocusedField"] =
mock<ElementWithOpId<FormFieldElement>>();
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("input"));
@ -461,9 +442,10 @@ describe("AutofillOverlayContentService", () => {
});
it("stores the field as a user filled field if the form field data indicates that it is for a username", async () => {
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("input"));
@ -477,9 +459,10 @@ describe("AutofillOverlayContentService", () => {
"password-field",
) as ElementWithOpId<FormFieldElement>;
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
passwordFieldElement,
autofillFieldData,
pageDetailsMock,
);
passwordFieldElement.dispatchEvent(new Event("input"));
@ -492,9 +475,10 @@ describe("AutofillOverlayContentService", () => {
jest.spyOn(autofillOverlayContentService as any, "isUserAuthed").mockReturnValue(false);
(autofillFieldElement as HTMLInputElement).value = "test";
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("input"));
await flushPromises();
@ -513,9 +497,10 @@ describe("AutofillOverlayContentService", () => {
(autofillFieldElement as HTMLInputElement).value = "test";
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("input"));
await flushPromises();
@ -530,9 +515,10 @@ describe("AutofillOverlayContentService", () => {
jest.spyOn(autofillOverlayContentService as any, "openInlineMenu");
(autofillFieldElement as HTMLInputElement).value = "";
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("input"));
await flushPromises();
@ -545,9 +531,10 @@ describe("AutofillOverlayContentService", () => {
jest.spyOn(autofillOverlayContentService as any, "openInlineMenu");
(autofillFieldElement as HTMLInputElement).value = "";
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("input"));
await flushPromises();
@ -563,9 +550,10 @@ describe("AutofillOverlayContentService", () => {
jest.spyOn(autofillOverlayContentService as any, "openInlineMenu");
(autofillFieldElement as HTMLInputElement).value = "";
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("input"));
await flushPromises();
@ -579,9 +567,10 @@ describe("AutofillOverlayContentService", () => {
jest
.spyOn(autofillOverlayContentService as any, "triggerFormFieldFocusedAction")
.mockImplementation();
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
});
@ -635,9 +624,10 @@ describe("AutofillOverlayContentService", () => {
autofillOverlayContentService["mostRecentlyFocusedField"] = autofillFieldElement;
autofillOverlayContentService["inlineMenuVisibility"] =
AutofillOverlayVisibility.OnFieldFocus;
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("focus"));
@ -647,9 +637,10 @@ describe("AutofillOverlayContentService", () => {
});
it("updates the most recently focused field", async () => {
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("focus"));
@ -664,9 +655,10 @@ describe("AutofillOverlayContentService", () => {
it("removes the overlay list if the autofill visibility is set to onClick", async () => {
autofillOverlayContentService["inlineMenuVisibility"] =
AutofillOverlayVisibility.OnButtonClick;
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("focus"));
@ -683,9 +675,10 @@ describe("AutofillOverlayContentService", () => {
"input",
) as ElementWithOpId<HTMLInputElement>;
(autofillFieldElement as HTMLInputElement).value = "test";
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("focus"));
@ -701,9 +694,10 @@ describe("AutofillOverlayContentService", () => {
(autofillFieldElement as HTMLInputElement).value = "";
autofillOverlayContentService["inlineMenuVisibility"] =
AutofillOverlayVisibility.OnFieldFocus;
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("focus"));
@ -717,9 +711,10 @@ describe("AutofillOverlayContentService", () => {
autofillOverlayContentService["inlineMenuVisibility"] =
AutofillOverlayVisibility.OnFieldFocus;
jest.spyOn(autofillOverlayContentService as any, "isUserAuthed").mockReturnValue(true);
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("focus"));
@ -735,9 +730,10 @@ describe("AutofillOverlayContentService", () => {
jest
.spyOn(autofillOverlayContentService as any, "isInlineMenuCiphersPopulated")
.mockReturnValue(true);
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("focus"));
@ -752,9 +748,10 @@ describe("AutofillOverlayContentService", () => {
describe("hidden form field focus event", () => {
it("sets up the inline menu listeners if the autofill field data is in the cache", async () => {
autofillFieldData.viewable = false;
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillFieldElement.dispatchEvent(new Event("focus"));
@ -785,9 +782,10 @@ describe("AutofillOverlayContentService", () => {
it("skips setting up the inline menu listeners if the autofill field data is not in the cache", async () => {
autofillFieldData.viewable = false;
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillOverlayContentService["formFieldElements"].delete(autofillFieldElement);
@ -821,9 +819,10 @@ describe("AutofillOverlayContentService", () => {
writable: true,
});
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("openAutofillInlineMenu");
@ -835,9 +834,10 @@ describe("AutofillOverlayContentService", () => {
it("sets the most recently focused field to the passed form field element if the value is not set", async () => {
autofillOverlayContentService["mostRecentlyFocusedField"] = undefined;
await autofillOverlayContentService.setupInlineMenuListenerOnField(
await autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
expect(autofillOverlayContentService["mostRecentlyFocusedField"]).toEqual(
@ -1608,8 +1608,8 @@ describe("AutofillOverlayContentService", () => {
expect(sendExtensionMessageSpy).toHaveBeenCalledWith("updateSubFrameData", {
subFrameData: {
frameId: 10,
left: 168,
top: 168,
left: expect.any(Number),
top: expect.any(Number),
url: "https://example.com/",
parentFrameIds: [1, 2, 3, 4],
subFrameDepth: expect.any(Number),
@ -1652,6 +1652,7 @@ describe("AutofillOverlayContentService", () => {
describe("destroy", () => {
let autofillFieldElement: ElementWithOpId<FormFieldElement>;
let autofillFieldData: AutofillField;
let pageDetailsMock: AutofillPageDetails;
beforeEach(() => {
document.body.innerHTML = `
@ -1671,11 +1672,21 @@ describe("AutofillOverlayContentService", () => {
placeholder: "username",
elementNumber: 1,
});
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
autofillOverlayContentService.setupInlineMenuListenerOnField(
const passwordFieldData = createAutofillFieldMock({
opid: "password-field",
form: "validFormId",
elementNumber: 2,
autocompleteType: "current-password",
type: "password",
});
pageDetailsMock = mock<AutofillPageDetails>({
forms: { validFormId: mock<AutofillForm>() },
fields: [autofillFieldData, passwordFieldData],
});
void autofillOverlayContentService.setupInlineMenu(
autofillFieldElement,
autofillFieldData,
pageDetailsMock,
);
autofillOverlayContentService["mostRecentlyFocusedField"] = autofillFieldElement;
});

View File

@ -16,6 +16,7 @@ import {
RedirectFocusDirection,
} from "../enums/autofill-overlay.enum";
import AutofillField from "../models/autofill-field";
import AutofillPageDetails from "../models/autofill-page-details";
import { ElementWithOpId, FillableFormFieldElement, FormFieldElement } from "../types";
import {
elementIsFillableFormField,
@ -30,6 +31,7 @@ import {
OpenAutofillInlineMenuOptions,
SubFrameDataFromWindowMessage,
} from "./abstractions/autofill-overlay-content.service";
import { InlineMenuFieldQualificationService } from "./abstractions/inline-menu-field-qualifications.service";
import { AutoFillConstants } from "./autofill-constants";
export class AutofillOverlayContentService implements AutofillOverlayContentServiceInterface {
@ -50,7 +52,6 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
private recalculateSubFrameOffsetsTimeout: number | NodeJS.Timeout;
private reflowPerformanceObserver: PerformanceObserver;
private reflowMutationObserver: MutationObserver;
private autofillFieldKeywordsMap: WeakMap<AutofillField, string> = new WeakMap();
private eventHandlersMemo: { [key: string]: EventListener } = {};
private readonly extensionMessageHandlers: AutofillOverlayContentExtensionMessageHandlers = {
openAutofillInlineMenu: ({ message }) => this.openInlineMenu(message),
@ -70,6 +71,8 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
destroyAutofillInlineMenuListeners: () => this.destroy(),
};
constructor(private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService) {}
/**
* Initializes the autofill overlay content service by setting up the mutation observers.
* The observers will be instantiated on DOMContentLoaded if the page is current loading.
@ -97,12 +100,17 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
*
* @param formFieldElement - Form field elements identified during the page details collection process.
* @param autofillFieldData - Autofill field data captured from the form field element.
* @param pageDetails - The collected page details from the tab.
*/
async setupInlineMenuListenerOnField(
async setupInlineMenu(
formFieldElement: ElementWithOpId<FormFieldElement>,
autofillFieldData: AutofillField,
pageDetails: AutofillPageDetails,
) {
if (this.isIgnoredField(autofillFieldData) || this.formFieldElements.has(formFieldElement)) {
if (
this.formFieldElements.has(formFieldElement) ||
this.isIgnoredField(autofillFieldData, pageDetails)
) {
return;
}
@ -110,6 +118,12 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
return;
}
await this.setupInlineMenuOnQualifiedField(formFieldElement);
}
private async setupInlineMenuOnQualifiedField(
formFieldElement: ElementWithOpId<FormFieldElement>,
) {
this.formFieldElements.add(formFieldElement);
if (!this.mostRecentlyFocusedField) {
@ -517,51 +531,6 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
return this.authStatus === AuthenticationStatus.Unlocked;
}
/**
* Identifies if the autofill field's data contains any of
* the keyboards matching the passed list of keywords.
*
* @param autofillFieldData - Autofill field data captured from the form field element.
* @param keywords - Keywords to search for in the autofill field data.
*/
private keywordsFoundInFieldData(autofillFieldData: AutofillField, keywords: string[]) {
const searchedString = this.getAutofillFieldDataKeywords(autofillFieldData);
return keywords.some((keyword) => searchedString.includes(keyword));
}
/**
* Aggregates the autofill field's data into a single string
* that can be used to search for keywords.
*
* @param autofillFieldData - Autofill field data captured from the form field element.
*/
private getAutofillFieldDataKeywords(autofillFieldData: AutofillField) {
if (this.autofillFieldKeywordsMap.has(autofillFieldData)) {
return this.autofillFieldKeywordsMap.get(autofillFieldData);
}
const keywordValues = [
autofillFieldData.htmlID,
autofillFieldData.htmlName,
autofillFieldData.htmlClass,
autofillFieldData.type,
autofillFieldData.title,
autofillFieldData.placeholder,
autofillFieldData.autoCompleteType,
autofillFieldData["label-data"],
autofillFieldData["label-aria"],
autofillFieldData["label-left"],
autofillFieldData["label-right"],
autofillFieldData["label-tag"],
autofillFieldData["label-top"],
]
.join(",")
.toLowerCase();
this.autofillFieldKeywordsMap.set(autofillFieldData, keywordValues);
return keywordValues;
}
/**
* Validates that the most recently focused field is currently
* focused within the root node relative to the field.
@ -698,20 +667,20 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
* updated in the future to support other types of forms.
*
* @param autofillFieldData - Autofill field data captured from the form field element.
* @param pageDetails - The collected page details from the tab.
*/
private isIgnoredField(autofillFieldData: AutofillField): boolean {
if (
this.ignoredFieldTypes.has(autofillFieldData.type) ||
this.keywordsFoundInFieldData(autofillFieldData, ["search", "captcha"])
) {
private isIgnoredField(
autofillFieldData: AutofillField,
pageDetails: AutofillPageDetails,
): boolean {
if (this.ignoredFieldTypes.has(autofillFieldData.type)) {
return true;
}
const isLoginCipherField =
autofillFieldData.type === "password" ||
this.keywordsFoundInFieldData(autofillFieldData, AutoFillConstants.UsernameFieldNames);
return !isLoginCipherField;
return !this.inlineMenuFieldQualificationService.isFieldForLoginForm(
autofillFieldData,
pageDetails,
);
}
/**
@ -732,7 +701,6 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
}
this.setupHiddenFieldFallbackListener(formFieldElement, autofillFieldData);
return true;
}
@ -775,7 +743,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ
autofillFieldData.readonly = getAttributeBoolean(formFieldElement, "disabled");
autofillFieldData.disabled = getAttributeBoolean(formFieldElement, "disabled");
autofillFieldData.viewable = true;
void this.setupInlineMenuListenerOnField(formFieldElement, autofillFieldData);
void this.setupInlineMenuOnQualifiedField(formFieldElement);
}
this.removeHiddenFieldFallbackListener(formFieldElement);

View File

@ -11,6 +11,7 @@ import {
FormElementWithAttribute,
} from "../types";
import { InlineMenuFieldQualificationService } from "./abstractions/inline-menu-field-qualifications.service";
import { AutofillOverlayContentService } from "./autofill-overlay-content.service";
import CollectAutofillContentService from "./collect-autofill-content.service";
import DomElementVisibilityService from "./dom-element-visibility.service";
@ -28,13 +29,17 @@ const waitForIdleCallback = () => new Promise((resolve) => globalThis.requestIdl
describe("CollectAutofillContentService", () => {
const domElementVisibilityService = new DomElementVisibilityService();
const autofillOverlayContentService = new AutofillOverlayContentService();
const inlineMenuFieldQualificationService = mock<InlineMenuFieldQualificationService>();
const autofillOverlayContentService = new AutofillOverlayContentService(
inlineMenuFieldQualificationService,
);
let collectAutofillContentService: CollectAutofillContentService;
const mockIntersectionObserver = mock<IntersectionObserver>();
const mockQuerySelectorAll = mockQuerySelectorAllDefinedCall();
beforeEach(() => {
globalThis.requestIdleCallback = jest.fn((cb, options) => setTimeout(cb, 100));
globalThis.cancelIdleCallback = jest.fn((id) => clearTimeout(id));
document.body.innerHTML = mockLoginForm;
collectAutofillContentService = new CollectAutofillContentService(
domElementVisibilityService,
@ -247,11 +252,16 @@ describe("CollectAutofillContentService", () => {
const isFormFieldViewableSpy = jest
.spyOn(collectAutofillContentService["domElementVisibilityService"], "isFormFieldViewable")
.mockResolvedValue(true);
const setupAutofillOverlayListenerOnFieldSpy = jest.spyOn(
collectAutofillContentService["autofillOverlayContentService"],
"setupInlineMenu",
);
await collectAutofillContentService.getPageDetails();
expect(autofillField.viewable).toBe(true);
expect(isFormFieldViewableSpy).toHaveBeenCalledWith(fieldElement);
expect(setupAutofillOverlayListenerOnFieldSpy).toHaveBeenCalled();
});
it("returns an object containing information about the current page as well as autofill data for the forms and fields of the page", async () => {
@ -1191,7 +1201,7 @@ describe("CollectAutofillContentService", () => {
"aria-disabled": false,
"aria-haspopup": false,
"aria-hidden": false,
autoCompleteType: null,
autoCompleteType: "off",
checked: false,
"data-stripe": hiddenField.dataStripe,
disabled: false,
@ -2558,7 +2568,7 @@ describe("CollectAutofillContentService", () => {
);
setupAutofillOverlayListenerOnFieldSpy = jest.spyOn(
collectAutofillContentService["autofillOverlayContentService"],
"setupInlineMenuListenerOnField",
"setupInlineMenu",
);
});
@ -2622,22 +2632,20 @@ describe("CollectAutofillContentService", () => {
expect(setupAutofillOverlayListenerOnFieldSpy).toHaveBeenCalledWith(
formFieldElement,
autofillField,
expect.anything(),
);
});
});
describe("destroy", () => {
it("clears the updateAutofillElementsAfterMutationTimeout", () => {
it("clears the updateAfterMutationIdleCallback", () => {
jest.spyOn(window, "clearTimeout");
collectAutofillContentService["updateAutofillElementsAfterMutationTimeout"] = setTimeout(
jest.fn,
100,
);
collectAutofillContentService["updateAfterMutationIdleCallback"] = setTimeout(jest.fn, 100);
collectAutofillContentService.destroy();
expect(clearTimeout).toHaveBeenCalledWith(
collectAutofillContentService["updateAutofillElementsAfterMutationTimeout"],
collectAutofillContentService["updateAfterMutationIdleCallback"],
);
});
});

View File

@ -7,26 +7,27 @@ import {
elementIsDescriptionTermElement,
elementIsFillableFormField,
elementIsFormElement,
elementIsInputElement,
elementIsLabelElement,
elementIsSelectElement,
elementIsSpanElement,
nodeIsElement,
elementIsInputElement,
elementIsTextAreaElement,
nodeIsFormElement,
nodeIsInputElement,
sendExtensionMessage,
// sendExtensionMessage,
getAttributeBoolean,
getPropertyOrAttribute,
requestIdleCallbackPolyfill,
cancelIdleCallbackPolyfill,
} from "../utils";
import { AutofillOverlayContentService } from "./abstractions/autofill-overlay-content.service";
import {
UpdateAutofillDataAttributeParams,
AutofillFieldElements,
AutofillFormElements,
CollectAutofillContentService as CollectAutofillContentServiceInterface,
UpdateAutofillDataAttributeParams,
} from "./abstractions/collect-autofill-content.service";
import { DomElementVisibilityService } from "./abstractions/dom-element-visibility.service";
@ -43,9 +44,9 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
private intersectionObserver: IntersectionObserver;
private elementInitializingIntersectionObserver: Set<Element> = new Set();
private mutationObserver: MutationObserver;
private updateAutofillElementsAfterMutationTimeout: number | NodeJS.Timeout;
private mutationsQueue: MutationRecord[][] = [];
private readonly updateAfterMutationTimeoutDelay = 1000;
private updateAfterMutationIdleCallback: NodeJS.Timeout | number;
private readonly updateAfterMutationTimeout = 1000;
private readonly formFieldQueryString;
private readonly nonInputFormFieldTags = new Set(["textarea", "select"]);
private readonly ignoredInputTypes = new Set([
@ -56,7 +57,7 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
"image",
"file",
]);
private useTreeWalkerStrategyFlagSet = false;
private useTreeWalkerStrategyFlagSet = true;
constructor(
domElementVisibilityService: DomElementVisibilityService,
@ -71,10 +72,10 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
}
this.formFieldQueryString = `${inputQuery}, textarea:not([data-bwignore]), select:not([data-bwignore]), span[data-bwautofill]`;
void sendExtensionMessage("getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag").then(
(useTreeWalkerStrategyFlag) =>
(this.useTreeWalkerStrategyFlagSet = !!useTreeWalkerStrategyFlag?.result),
);
// void sendExtensionMessage("getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag").then(
// (useTreeWalkerStrategyFlag) =>
// (this.useTreeWalkerStrategyFlagSet = !!useTreeWalkerStrategyFlag?.result),
// );
}
/**
@ -119,7 +120,10 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
}
this.domRecentlyMutated = false;
return this.getFormattedPageDetails(autofillFormsData, autofillFieldsData);
const pageDetails = this.getFormattedPageDetails(autofillFormsData, autofillFieldsData);
this.setupInlineMenuListeners(pageDetails);
return pageDetails;
}
/**
@ -277,14 +281,11 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
*/
private updateCachedAutofillFieldVisibility() {
this.autofillFieldElements.forEach(async (autofillField, element) => {
const currentViewableState = autofillField.viewable;
const previouslyViewable = autofillField.viewable;
autofillField.viewable = await this.domElementVisibilityService.isFormFieldViewable(element);
if (!currentViewableState && autofillField.viewable) {
await this.autofillOverlayContentService?.setupInlineMenuListenerOnField(
element,
autofillField,
);
if (!previouslyViewable && autofillField.viewable) {
this.setupInlineMenu(element, autofillField);
}
});
}
@ -458,10 +459,6 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
if (elementIsSpanElement(element)) {
this.cacheAutofillFieldElement(index, element, autofillFieldBase);
void this.autofillOverlayContentService?.setupInlineMenuListenerOnField(
element,
autofillFieldBase,
);
return autofillFieldBase;
}
@ -501,7 +498,6 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
};
this.cacheAutofillFieldElement(index, element, autofillField);
void this.autofillOverlayContentService?.setupInlineMenuListenerOnField(element, autofillField);
return autofillField;
};
@ -533,11 +529,11 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
* @private
*/
private getAutoCompleteAttribute(element: ElementWithOpId<FormFieldElement>): string {
const autoCompleteType =
return (
this.getPropertyOrAttribute(element, "x-autocompletetype") ||
this.getPropertyOrAttribute(element, "autocompletetype") ||
this.getPropertyOrAttribute(element, "autocomplete");
return autoCompleteType !== "off" ? autoCompleteType : null;
this.getPropertyOrAttribute(element, "autocomplete")
);
}
/**
@ -1196,13 +1192,13 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
* @private
*/
private updateAutofillElementsAfterMutation() {
if (this.updateAutofillElementsAfterMutationTimeout) {
clearTimeout(this.updateAutofillElementsAfterMutationTimeout);
if (this.updateAfterMutationIdleCallback) {
cancelIdleCallbackPolyfill(this.updateAfterMutationIdleCallback);
}
this.updateAutofillElementsAfterMutationTimeout = setTimeout(
this.updateAfterMutationIdleCallback = requestIdleCallbackPolyfill(
this.getPageDetails.bind(this),
this.updateAfterMutationTimeoutDelay,
{ timeout: this.updateAfterMutationTimeout },
);
}
@ -1392,22 +1388,64 @@ class CollectAutofillContentService implements CollectAutofillContentServiceInte
}
cachedAutofillFieldElement.viewable = true;
void this.autofillOverlayContentService?.setupInlineMenuListenerOnField(
formFieldElement,
cachedAutofillFieldElement,
);
this.setupInlineMenu(formFieldElement, cachedAutofillFieldElement);
this.intersectionObserver?.unobserve(entry.target);
}
};
/**
* Iterates over all cached field elements and sets up the inline menu listeners on each field.
*
* @param pageDetails - The page details to use for the inline menu listeners
*/
private setupInlineMenuListeners(pageDetails: AutofillPageDetails) {
if (!this.autofillOverlayContentService) {
return;
}
this.autofillFieldElements.forEach((autofillField, formFieldElement) => {
this.setupInlineMenu(formFieldElement, autofillField, pageDetails);
});
}
/**
* Sets up the inline menu listener on the passed field element.
*
* @param formFieldElement - The form field element to set up the inline menu listener on
* @param autofillField - The metadata for the form field
* @param pageDetails - The page details to use for the inline menu listeners
*/
private setupInlineMenu(
formFieldElement: ElementWithOpId<FormFieldElement>,
autofillField: AutofillField,
pageDetails?: AutofillPageDetails,
) {
if (!this.autofillOverlayContentService) {
return;
}
const autofillPageDetails =
pageDetails ||
this.getFormattedPageDetails(
this.getFormattedAutofillFormsData(),
this.getFormattedAutofillFieldsData(),
);
void this.autofillOverlayContentService.setupInlineMenu(
formFieldElement,
autofillField,
autofillPageDetails,
);
}
/**
* Destroys the CollectAutofillContentService. Clears all
* timeouts and disconnects the mutation observer.
*/
destroy() {
if (this.updateAutofillElementsAfterMutationTimeout) {
clearTimeout(this.updateAutofillElementsAfterMutationTimeout);
if (this.updateAfterMutationIdleCallback) {
cancelIdleCallbackPolyfill(this.updateAfterMutationIdleCallback);
}
this.mutationObserver?.disconnect();
this.intersectionObserver?.disconnect();

View File

@ -0,0 +1,662 @@
import { mock, MockProxy } from "jest-mock-extended";
import AutofillField from "../models/autofill-field";
import AutofillForm from "../models/autofill-form";
import AutofillPageDetails from "../models/autofill-page-details";
import { AutoFillConstants } from "./autofill-constants";
import { InlineMenuFieldQualificationService } from "./inline-menu-field-qualification.service";
describe("InlineMenuFieldQualificationService", () => {
let pageDetails: MockProxy<AutofillPageDetails>;
let inlineMenuFieldQualificationService: InlineMenuFieldQualificationService;
beforeEach(() => {
pageDetails = mock<AutofillPageDetails>({
forms: {},
fields: [],
});
inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
inlineMenuFieldQualificationService["inlineMenuFieldQualificationFlagSet"] = true;
});
describe("isFieldForLoginForm", () => {
describe("qualifying a password field for a login form", () => {
describe("an invalid password field", () => {
it("has a `new-password` autoCompleteType", () => {
const field = mock<AutofillField>({
type: "password",
autoCompleteType: "new-password",
});
expect(inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails)).toBe(
false,
);
});
it("has a type that is an excluded type", () => {
AutoFillConstants.ExcludedAutofillLoginTypes.forEach((excludedType) => {
const field = mock<AutofillField>({
type: excludedType,
});
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(false);
});
});
it("has an attribute present on the FieldIgnoreList, indicating that the field is a captcha", () => {
AutoFillConstants.FieldIgnoreList.forEach((attribute, index) => {
const field = mock<AutofillField>({
type: "password",
htmlID: index === 0 ? attribute : "",
htmlName: index === 1 ? attribute : "",
placeholder: index > 1 ? attribute : "",
});
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(false);
});
});
it("has a type other than `password` or `text`", () => {
const field = mock<AutofillField>({
type: "number",
htmlID: "not-password",
htmlName: "not-password",
placeholder: "not-password",
});
expect(inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails)).toBe(
false,
);
});
it("has a type of `text` without an attribute that indicates the field is a password field", () => {
const field = mock<AutofillField>({
type: "text",
htmlID: "something-else",
htmlName: "something-else",
placeholder: "something-else",
});
expect(inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails)).toBe(
false,
);
});
it("has a type of `text` and contains attributes that indicates the field is a search field", () => {
const field = mock<AutofillField>({
type: "text",
htmlID: "search",
htmlName: "something-else",
placeholder: "something-else",
});
expect(inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails)).toBe(
false,
);
});
describe("does not have a parent form element", () => {
beforeEach(() => {
pageDetails.forms = {};
});
it("on a page that has more than one password field", () => {
const field = mock<AutofillField>({
type: "password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
form: "",
});
const secondField = mock<AutofillField>({
type: "password",
htmlID: "some-other-password",
htmlName: "some-other-password",
placeholder: "some-other-password",
});
pageDetails.fields = [field, secondField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(false);
});
it("on a page that has more than one visible username field", () => {
const field = mock<AutofillField>({
type: "password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
form: "",
});
const usernameField = mock<AutofillField>({
type: "text",
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
});
const secondUsernameField = mock<AutofillField>({
type: "text",
htmlID: "some-other-user-username",
htmlName: "some-other-user-username",
placeholder: "some-other-user-username",
});
pageDetails.fields = [field, usernameField, secondUsernameField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(false);
});
it("has a disabled `autocompleteType` value", () => {
const field = mock<AutofillField>({
type: "password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
form: "",
autoCompleteType: "off",
});
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(false);
});
});
describe("has a parent form element", () => {
let form: MockProxy<AutofillForm>;
beforeEach(() => {
form = mock<AutofillForm>({ opid: "validFormId" });
pageDetails.forms = {
validFormId: form,
};
});
it("is structured with other password fields in the same form", () => {
const field = mock<AutofillField>({
type: "password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
form: "validFormId",
});
const secondField = mock<AutofillField>({
type: "password",
htmlID: "some-other-password",
htmlName: "some-other-password",
placeholder: "some-other-password",
form: "validFormId",
});
pageDetails.fields = [field, secondField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(false);
});
});
});
describe("a valid password field", () => {
it("has an autoCompleteType of `current-password`", () => {
const field = mock<AutofillField>({
type: "password",
autoCompleteType: "current-password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
});
expect(inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails)).toBe(
true,
);
});
it("has a type of `text` with an attribute that indicates the field is a password field", () => {
const field = mock<AutofillField>({
type: "text",
htmlID: null,
htmlName: "user-password",
placeholder: "user-password",
});
expect(inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails)).toBe(
true,
);
});
describe("does not have a parent form element", () => {
it("is the only password field on the page, has one username field on the page, and has a non-disabled `autocompleteType` value", () => {
pageDetails.forms = {};
const field = mock<AutofillField>({
type: "password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
form: "",
autoCompleteType: "current-password",
});
const usernameField = mock<AutofillField>({
type: "text",
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
});
pageDetails.fields = [field, usernameField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(true);
});
});
describe("has a parent form element", () => {
let form: MockProxy<AutofillForm>;
beforeEach(() => {
form = mock<AutofillForm>({ opid: "validFormId" });
pageDetails.forms = {
validFormId: form,
};
});
it("is the only password field within the form and has a visible username field", () => {
const field = mock<AutofillField>({
type: "password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
form: "validFormId",
});
const secondPasswordField = mock<AutofillField>({
type: "password",
htmlID: "some-other-password",
htmlName: "some-other-password",
placeholder: "some-other-password",
form: "anotherFormId",
});
const usernameField = mock<AutofillField>({
type: "text",
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
form: "validFormId",
});
pageDetails.fields = [field, secondPasswordField, usernameField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(true);
});
it("is the only password field within the form and has a non-disabled `autocompleteType` value", () => {
const field = mock<AutofillField>({
type: "password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
form: "validFormId",
autoCompleteType: "",
});
const secondPasswordField = mock<AutofillField>({
type: "password",
htmlID: "some-other-password",
htmlName: "some-other-password",
placeholder: "some-other-password",
form: "anotherFormId",
});
pageDetails.fields = [field, secondPasswordField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(true);
});
});
});
});
describe("qualifying a username field for a login form", () => {
describe("an invalid username field", () => {
["username", "email"].forEach((autoCompleteType) => {
it(`has a ${autoCompleteType} 'autoCompleteType' value when structured on a page with new password fields`, () => {
const field = mock<AutofillField>({
type: "text",
autoCompleteType,
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
});
const passwordField = mock<AutofillField>({
type: "password",
autoCompleteType: "new-password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
});
pageDetails.fields = [field, passwordField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(false);
});
});
["new", "change", "neue", "ändern"].forEach((keyword) => {
it(`has a keyword of ${keyword} that indicates a 'new or changed' username is being filled`, () => {
const field = mock<AutofillField>({
type: "text",
autoCompleteType: "",
htmlID: "user-username",
htmlName: "user-username",
placeholder: `${keyword} username`,
});
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(false);
});
});
describe("does not have a parent form element", () => {
beforeEach(() => {
pageDetails.forms = {};
});
it("is structured on a page with multiple password fields", () => {
const field = mock<AutofillField>({
type: "text",
autoCompleteType: "",
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
});
const passwordField = mock<AutofillField>({
type: "password",
autoCompleteType: "current-password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
});
const secondPasswordField = mock<AutofillField>({
type: "password",
autoCompleteType: "current-password",
htmlID: "some-other-password",
htmlName: "some-other-password",
placeholder: "some-other-password",
});
pageDetails.fields = [field, passwordField, secondPasswordField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(false);
});
});
describe("has a parent form element", () => {
let form: MockProxy<AutofillForm>;
beforeEach(() => {
form = mock<AutofillForm>({ opid: "validFormId" });
pageDetails.forms = {
validFormId: form,
};
});
it("is structured on a page with no password fields and has a disabled `autoCompleteType` value", () => {
const field = mock<AutofillField>({
type: "text",
autoCompleteType: "off",
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
form: "validFormId",
});
pageDetails.fields = [field];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(false);
});
it("is structured on a page with no password fields but has other types of fields in the form", () => {
const field = mock<AutofillField>({
type: "text",
autoCompleteType: "",
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
form: "validFormId",
});
const otherField = mock<AutofillField>({
type: "number",
autoCompleteType: "",
htmlID: "some-other-field",
htmlName: "some-other-field",
placeholder: "some-other-field",
form: "validFormId",
});
pageDetails.fields = [field, otherField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(false);
});
it("is structured on a page with multiple viewable password field", () => {
const field = mock<AutofillField>({
type: "text",
autoCompleteType: "",
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
form: "validFormId",
});
const passwordField = mock<AutofillField>({
type: "password",
autoCompleteType: "current-password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
form: "validFormId",
});
const secondPasswordField = mock<AutofillField>({
type: "password",
autoCompleteType: "current-password",
htmlID: "some-other-password",
htmlName: "some-other-password",
placeholder: "some-other-password",
form: "validFormId",
});
pageDetails.fields = [field, passwordField, secondPasswordField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(false);
});
it("is structured on a page with a with no visible password fields and but contains a disabled autocomplete type", () => {
const field = mock<AutofillField>({
type: "text",
autoCompleteType: "off",
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
form: "validFormId",
});
const passwordField = mock<AutofillField>({
type: "password",
autoCompleteType: "current-password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
form: "validFormId",
viewable: false,
});
pageDetails.fields = [field, passwordField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(false);
});
});
});
describe("a valid username field", () => {
["username", "email"].forEach((autoCompleteType) => {
it(`has a ${autoCompleteType} 'autoCompleteType' value`, () => {
const field = mock<AutofillField>({
type: "text",
autoCompleteType,
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
});
const passwordField = mock<AutofillField>({
type: "password",
autoCompleteType: "current-password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
});
pageDetails.fields = [field, passwordField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(true);
});
});
describe("does not have a parent form element", () => {
beforeEach(() => {
pageDetails.forms = {};
});
it("is structured on a page with a single visible password field", () => {
const field = mock<AutofillField>({
type: "text",
autoCompleteType: "off",
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
});
const passwordField = mock<AutofillField>({
type: "password",
autoCompleteType: "current-password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
});
pageDetails.fields = [field, passwordField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(true);
});
it("is structured on a page with a single non-visible password field", () => {
const field = mock<AutofillField>({
type: "text",
autoCompleteType: "off",
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
});
const passwordField = mock<AutofillField>({
type: "password",
autoCompleteType: "current-password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
viewable: false,
});
pageDetails.fields = [field, passwordField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(true);
});
it("has a non-disabled autoCompleteType and is structured on a page with no other password fields", () => {
const field = mock<AutofillField>({
type: "text",
autoCompleteType: "",
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
});
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(true);
});
});
describe("has a parent form element", () => {
let form: MockProxy<AutofillForm>;
beforeEach(() => {
form = mock<AutofillForm>({ opid: "validFormId" });
pageDetails.forms = {
validFormId: form,
};
});
it("is structured on a page with a single password field", () => {
const field = mock<AutofillField>({
type: "text",
autoCompleteType: "",
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
form: "validFormId",
});
const passwordField = mock<AutofillField>({
type: "password",
autoCompleteType: "current-password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
form: "validFormId",
});
pageDetails.fields = [field, passwordField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(true);
});
it("is structured on a page with a with no visible password fields and a non-disabled autocomplete type", () => {
const field = mock<AutofillField>({
type: "text",
autoCompleteType: "",
htmlID: "user-username",
htmlName: "user-username",
placeholder: "user-username",
form: "validFormId",
});
const passwordField = mock<AutofillField>({
type: "password",
autoCompleteType: "current-password",
htmlID: "user-password",
htmlName: "user-password",
placeholder: "user-password",
form: "validFormId",
viewable: false,
});
pageDetails.fields = [field, passwordField];
expect(
inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails),
).toBe(true);
});
});
});
});
});
});

View File

@ -0,0 +1,438 @@
import AutofillField from "../models/autofill-field";
import AutofillPageDetails from "../models/autofill-page-details";
import { sendExtensionMessage } from "../utils";
import { InlineMenuFieldQualificationService as InlineMenuFieldQualificationsServiceInterface } from "./abstractions/inline-menu-field-qualifications.service";
import { AutoFillConstants } from "./autofill-constants";
export class InlineMenuFieldQualificationService
implements InlineMenuFieldQualificationsServiceInterface
{
private searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames);
private excludedAutofillLoginTypesSet = new Set(AutoFillConstants.ExcludedAutofillLoginTypes);
private usernameFieldTypes = new Set(["text", "email", "number", "tel"]);
private usernameAutocompleteValues = new Set(["username", "email"]);
private fieldIgnoreListString = AutoFillConstants.FieldIgnoreList.join(",");
private passwordFieldExcludeListString = AutoFillConstants.PasswordFieldExcludeList.join(",");
private autofillFieldKeywordsMap: WeakMap<AutofillField, string> = new WeakMap();
private autocompleteDisabledValues = new Set(["off", "false"]);
private newFieldKeywords = new Set(["new", "change", "neue", "ändern"]);
private inlineMenuFieldQualificationFlagSet = false;
constructor() {
void sendExtensionMessage("getInlineMenuFieldQualificationFeatureFlag").then(
(getInlineMenuFieldQualificationFlag) =>
(this.inlineMenuFieldQualificationFlagSet = !!getInlineMenuFieldQualificationFlag?.result),
);
}
/**
* Validates the provided field as a field for a login form.
*
* @param field - The field to validate, should be a username or password field.
* @param pageDetails - The details of the page that the field is on.
*/
isFieldForLoginForm(field: AutofillField, pageDetails: AutofillPageDetails): boolean {
if (!this.inlineMenuFieldQualificationFlagSet) {
return this.isFieldForLoginFormFallback(field);
}
const isCurrentPasswordField = this.isCurrentPasswordField(field);
if (isCurrentPasswordField) {
return this.isPasswordFieldForLoginForm(field, pageDetails);
}
const isUsernameField = this.isUsernameField(field);
if (!isUsernameField) {
return false;
}
return this.isUsernameFieldForLoginForm(field, pageDetails);
}
/**
* Validates the provided field as a password field for a login form.
*
* @param field - The field to validate
* @param pageDetails - The details of the page that the field is on.
*/
private isPasswordFieldForLoginForm(
field: AutofillField,
pageDetails: AutofillPageDetails,
): boolean {
// If the provided field is set with an autocomplete value of "current-password", we should assume that
// the page developer intends for this field to be interpreted as a password field for a login form.
if (field.autoCompleteType === "current-password") {
return true;
}
const usernameFieldsInPageDetails = pageDetails.fields.filter(this.isUsernameField);
const passwordFieldsInPageDetails = pageDetails.fields.filter(this.isCurrentPasswordField);
// If a single username and a single password field exists on the page, we
// should assume that this field is part of a login form.
if (usernameFieldsInPageDetails.length === 1 && passwordFieldsInPageDetails.length === 1) {
return true;
}
// If the field is not structured within a form, we need to identify if the field is present on
// a page with multiple password fields. If that isn't the case, we can assume this is a login form field.
const parentForm = pageDetails.forms[field.form];
if (!parentForm) {
// If no parent form is found, and multiple password fields are present, we should assume that
// the passed field belongs to a user account creation form.
if (passwordFieldsInPageDetails.length > 1) {
return false;
}
// If multiple username fields exist on the page, we should assume that
// the provided field is part of an account creation form.
const visibleUsernameFields = usernameFieldsInPageDetails.filter((f) => f.viewable);
if (visibleUsernameFields.length > 1) {
return false;
}
// If a single username field or less is present on the page, then we can assume that the
// provided field is for a login form. This will only be the case if the field does not
// explicitly have its autocomplete attribute set to "off" or "false".
return !this.autocompleteDisabledValues.has(field.autoCompleteType);
}
// If the field has a form parent and there are multiple visible password fields
// in the form, this is not a login form field
const visiblePasswordFieldsInPageDetails = passwordFieldsInPageDetails.filter(
(f) => f.form === field.form && f.viewable,
);
if (visiblePasswordFieldsInPageDetails.length > 1) {
return false;
}
// If the form has any visible username fields, we should treat the field as part of a login form
const visibleUsernameFields = usernameFieldsInPageDetails.filter(
(f) => f.form === field.form && f.viewable,
);
if (visibleUsernameFields.length > 0) {
return true;
}
// If the field has a form parent and no username field exists and the field has an
// autocomplete attribute set to "off" or "false", this is not a password field
return !this.autocompleteDisabledValues.has(field.autoCompleteType);
}
/**
* Validates the provided field as a username field for a login form.
*
* @param field - The field to validate
* @param pageDetails - The details of the page that the field is on.
*/
private isUsernameFieldForLoginForm(
field: AutofillField,
pageDetails: AutofillPageDetails,
): boolean {
// If the provided field is set with an autocomplete of "username", we should assume that
// the page developer intends for this field to be interpreted as a username field.
if (this.usernameAutocompleteValues.has(field.autoCompleteType)) {
const newPasswordFieldsInPageDetails = pageDetails.fields.filter(this.isNewPasswordField);
return newPasswordFieldsInPageDetails.length === 0;
}
// If any keywords in the field's data indicates that this is a field for a "new" or "changed"
// username, we should assume that this field is not for a login form.
if (this.keywordsFoundInFieldData(field, [...this.newFieldKeywords])) {
return false;
}
// If the field is not explicitly set as a username field, we need to qualify
// the field based on the other fields that are present on the page.
const parentForm = pageDetails.forms[field.form];
const passwordFieldsInPageDetails = pageDetails.fields.filter(this.isCurrentPasswordField);
// If the field is not structured within a form, we need to identify if the field is used in conjunction
// with a password field. If that's the case, then we should assume that it is a form field element.
if (!parentForm) {
// If a formless field is present in a webpage with a single password field, we
// should assume that it is part of a login workflow.
const visiblePasswordFieldsInPageDetails = passwordFieldsInPageDetails.filter(
(passwordField) => passwordField.viewable,
);
if (visiblePasswordFieldsInPageDetails.length === 1) {
return true;
}
// If more than a single password field exists on the page, we should assume that the field
// is part of an account creation form.
if (visiblePasswordFieldsInPageDetails.length > 1) {
return false;
}
// If no visible fields are found on the page, but we have a single password
// field we should assume that the field is part of a login form.
if (passwordFieldsInPageDetails.length === 1) {
return true;
}
// If the page does not contain any password fields, it might be part of a multistep login form.
// That will only be the case if the field does not explicitly have its autocomplete attribute
// set to "off" or "false".
return !this.autocompleteDisabledValues.has(field.autoCompleteType);
}
// If the field is structured within a form, but no password fields are present in the form,
// we need to consider whether the field is part of a multistep login form.
if (passwordFieldsInPageDetails.length === 0) {
// If the field's autocomplete is set to a disabled value, we should assume that the field is
// not part of a login form.
if (this.autocompleteDisabledValues.has(field.autoCompleteType)) {
return false;
}
// If the form that contains the field has more than one visible field, we should assume
// that the field is part of an account creation form.
const fieldsWithinForm = pageDetails.fields.filter(
(pageDetailsField) => pageDetailsField.form === field.form && pageDetailsField.viewable,
);
return fieldsWithinForm.length === 1;
}
// If a single password field exists within the page details, and that password field is part of
// the same form as the provided field, we should assume that the field is part of a login form.
const visiblePasswordFieldsInPageDetails = passwordFieldsInPageDetails.filter(
(passwordField) => passwordField.form === field.form && passwordField.viewable,
);
if (visiblePasswordFieldsInPageDetails.length === 1) {
return true;
}
// If multiple visible password fields exist within the page details, we need to assume that the
// provided field is part of an account creation form.
if (visiblePasswordFieldsInPageDetails.length > 1) {
return false;
}
// If no visible password fields are found, this field might be part of a multipart form.
// Check for an invalid autocompleteType to determine if the field is part of a login form.
return !this.autocompleteDisabledValues.has(field.autoCompleteType);
}
/**
* Validates the provided field as a username field.
*
* @param field - The field to validate
*/
private isUsernameField = (field: AutofillField): boolean => {
if (
!this.usernameFieldTypes.has(field.type) ||
this.isExcludedFieldType(field, this.excludedAutofillLoginTypesSet)
) {
return false;
}
return this.keywordsFoundInFieldData(field, AutoFillConstants.UsernameFieldNames);
};
/**
* Validates the provided field as a current password field.
*
* @param field - The field to validate
*/
private isCurrentPasswordField = (field: AutofillField): boolean => {
if (field.autoCompleteType === "new-password") {
return false;
}
return this.isPasswordField(field);
};
/**
* Validates the provided field as a new password field.
*
* @param field - The field to validate
*/
private isNewPasswordField = (field: AutofillField): boolean => {
if (field.autoCompleteType === "current-password") {
return false;
}
return this.isPasswordField(field);
};
/**
* Validates the provided field as a password field.
*
* @param field - The field to validate
*/
private isPasswordField = (field: AutofillField): boolean => {
const isInputPasswordType = field.type === "password";
if (
(!isInputPasswordType &&
this.isExcludedFieldType(field, this.excludedAutofillLoginTypesSet)) ||
this.fieldHasDisqualifyingAttributeValue(field)
) {
return false;
}
return isInputPasswordType || this.isLikePasswordField(field);
};
/**
* Validates the provided field as a field to indicate if the
* field potentially acts as a password field.
*
* @param field - The field to validate
*/
private isLikePasswordField(field: AutofillField): boolean {
if (field.type !== "text") {
return false;
}
const testedValues = [field.htmlID, field.htmlName, field.placeholder];
for (let i = 0; i < testedValues.length; i++) {
if (this.valueIsLikePassword(testedValues[i])) {
return true;
}
}
return false;
}
/**
* Validates the provided value to indicate if the value is like a password.
*
* @param value - The value to validate
*/
private valueIsLikePassword(value: string): boolean {
if (value == null) {
return false;
}
// Removes all whitespace, _ and - characters
const cleanedValue = value.toLowerCase().replace(/[\s_-]/g, "");
if (cleanedValue.indexOf("password") < 0) {
return false;
}
return !(this.passwordFieldExcludeListString.indexOf(cleanedValue) > -1);
}
/**
* Validates the provided field to indicate if the field has a
* disqualifying attribute that would impede autofill entirely.
*
* @param field - The field to validate
*/
private fieldHasDisqualifyingAttributeValue(field: AutofillField): boolean {
const checkedAttributeValues = [field.htmlID, field.htmlName, field.placeholder];
for (let i = 0; i < checkedAttributeValues.length; i++) {
const checkedAttributeValue = checkedAttributeValues[i];
const cleanedValue = checkedAttributeValue?.toLowerCase().replace(/[\s_-]/g, "");
if (cleanedValue && this.fieldIgnoreListString.indexOf(cleanedValue) > -1) {
return true;
}
}
return false;
}
/**
* Validates the provided field to indicate if the field is excluded from autofill.
*
* @param field - The field to validate
* @param excludedTypes - The set of excluded types
*/
private isExcludedFieldType(field: AutofillField, excludedTypes: Set<string>): boolean {
if (excludedTypes.has(field.type)) {
return true;
}
return this.isSearchField(field);
}
/**
* Validates the provided field to indicate if the field is a search field.
*
* @param field - The field to validate
*/
private isSearchField(field: AutofillField): boolean {
const matchFieldAttributeValues = [field.type, field.htmlName, field.htmlID, field.placeholder];
for (let attrIndex = 0; attrIndex < matchFieldAttributeValues.length; attrIndex++) {
if (!matchFieldAttributeValues[attrIndex]) {
continue;
}
// Separate camel case words and case them to lower case values
const camelCaseSeparatedFieldAttribute = matchFieldAttributeValues[attrIndex]
.replace(/([a-z])([A-Z])/g, "$1 $2")
.toLowerCase();
// Split the attribute by non-alphabetical characters to get the keywords
const attributeKeywords = camelCaseSeparatedFieldAttribute.split(/[^a-z]/gi);
for (let keywordIndex = 0; keywordIndex < attributeKeywords.length; keywordIndex++) {
if (this.searchFieldNamesSet.has(attributeKeywords[keywordIndex])) {
return true;
}
}
}
return false;
}
/**
* Validates the provided field to indicate if the field has any of the provided keywords.
*
* @param autofillFieldData - The field data to search for keywords
* @param keywords - The keywords to search for
*/
private keywordsFoundInFieldData(autofillFieldData: AutofillField, keywords: string[]) {
const searchedString = this.getAutofillFieldDataKeywords(autofillFieldData);
return keywords.some((keyword) => searchedString.includes(keyword));
}
/**
* Retrieves the keywords from the provided autofill field data.
*
* @param autofillFieldData - The field data to search for keywords
*/
private getAutofillFieldDataKeywords(autofillFieldData: AutofillField) {
if (this.autofillFieldKeywordsMap.has(autofillFieldData)) {
return this.autofillFieldKeywordsMap.get(autofillFieldData);
}
const keywordValues = [
autofillFieldData.htmlID,
autofillFieldData.htmlName,
autofillFieldData.htmlClass,
autofillFieldData.type,
autofillFieldData.title,
autofillFieldData.placeholder,
autofillFieldData.autoCompleteType,
autofillFieldData["label-data"],
autofillFieldData["label-aria"],
autofillFieldData["label-left"],
autofillFieldData["label-right"],
autofillFieldData["label-tag"],
autofillFieldData["label-top"],
]
.join(",")
.toLowerCase();
this.autofillFieldKeywordsMap.set(autofillFieldData, keywordValues);
return keywordValues;
}
/**
* This method represents the previous rudimentary approach to qualifying fields for login forms.
*
* @param field - The field to validate
* @deprecated - This method will only be used when the fallback flag is set to true.
*/
private isFieldForLoginFormFallback(field: AutofillField): boolean {
if (field.type === "password") {
return true;
}
return this.isUsernameField(field);
}
}

View File

@ -1,9 +1,12 @@
import { mock } from "jest-mock-extended";
import { EVENTS } from "@bitwarden/common/autofill/constants";
import AutofillScript, { FillScript, FillScriptActions } from "../models/autofill-script";
import { mockQuerySelectorAllDefinedCall } from "../spec/testing-utils";
import { FillableFormFieldElement, FormElementWithAttribute, FormFieldElement } from "../types";
import { InlineMenuFieldQualificationService } from "./abstractions/inline-menu-field-qualifications.service";
import { AutofillOverlayContentService } from "./autofill-overlay-content.service";
import CollectAutofillContentService from "./collect-autofill-content.service";
import DomElementVisibilityService from "./dom-element-visibility.service";
@ -64,8 +67,11 @@ function setMockWindowLocation({
}
describe("InsertAutofillContentService", () => {
const inlineMenuFieldQualificationService = mock<InlineMenuFieldQualificationService>();
const domElementVisibilityService = new DomElementVisibilityService();
const autofillOverlayContentService = new AutofillOverlayContentService();
const autofillOverlayContentService = new AutofillOverlayContentService(
inlineMenuFieldQualificationService,
);
const collectAutofillContentService = new CollectAutofillContentService(
domElementVisibilityService,
autofillOverlayContentService,

View File

@ -21,7 +21,10 @@ export function generateRandomChars(length: number): string {
* @param callback - The callback function to run when the browser is idle.
* @param options - The options to pass to the requestIdleCallback function.
*/
export function requestIdleCallbackPolyfill(callback: () => void, options?: Record<string, any>) {
export function requestIdleCallbackPolyfill(
callback: () => void,
options?: Record<string, any>,
): number | NodeJS.Timeout {
if ("requestIdleCallback" in globalThis) {
return globalThis.requestIdleCallback(() => callback(), options);
}
@ -29,6 +32,19 @@ export function requestIdleCallbackPolyfill(callback: () => void, options?: Reco
return globalThis.setTimeout(() => callback(), 1);
}
/**
* Polyfills the cancelIdleCallback API with a clearTimeout fallback.
*
* @param id - The ID of the idle callback to cancel.
*/
export function cancelIdleCallbackPolyfill(id: NodeJS.Timeout | number) {
if ("cancelIdleCallback" in globalThis) {
return globalThis.cancelIdleCallback(id as number);
}
return globalThis.clearTimeout(id);
}
/**
* Generates a random string of characters that formatted as a custom element name.
*/

View File

@ -745,7 +745,6 @@ export default class MainBackground {
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
this.userVerificationService = new UserVerificationService(
this.stateService,
this.cryptoService,
this.accountService,
this.masterPasswordService,

View File

@ -69,6 +69,7 @@ export default class RuntimeBackground {
const messagesWithResponse = [
"biometricUnlock",
"getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag",
"getInlineMenuFieldQualificationFeatureFlag",
];
if (messagesWithResponse.includes(msg.command)) {
@ -186,6 +187,9 @@ export default class RuntimeBackground {
FeatureFlag.UseTreeWalkerApiForPageDetailsCollection,
);
}
case "getInlineMenuFieldQualificationFeatureFlag": {
return await this.configService.getFeatureFlag(FeatureFlag.InlineMenuFieldQualification);
}
}
}

View File

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "__MSG_extName__",
"short_name": "__MSG_appName__",
"version": "2024.6.0",
"version": "2024.6.1",
"description": "__MSG_extDesc__",
"default_locale": "en",
"author": "Bitwarden Inc.",

View File

@ -3,7 +3,7 @@
"minimum_chrome_version": "102.0",
"name": "__MSG_extName__",
"short_name": "__MSG_appName__",
"version": "2024.6.0",
"version": "2024.6.1",
"description": "__MSG_extDesc__",
"default_locale": "en",
"author": "Bitwarden Inc.",

View File

@ -8,6 +8,16 @@ import {
tdeDecryptionRequiredGuard,
unauthGuardFn,
} from "@bitwarden/angular/auth/guards";
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
import {
AnonLayoutWrapperComponent,
AnonLayoutWrapperData,
RegistrationFinishComponent,
RegistrationStartComponent,
RegistrationStartSecondaryComponent,
RegistrationStartSecondaryComponentData,
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { fido2AuthGuard } from "../auth/guards/fido2-auth.guard";
import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component";
@ -343,6 +353,45 @@ const routes: Routes = [
canActivate: [AuthGuard],
data: { state: "update-temp-password" },
},
{
path: "",
component: AnonLayoutWrapperComponent,
children: [
{
path: "signup",
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
data: { pageTitle: "createAccount" } satisfies AnonLayoutWrapperData,
children: [
{
path: "",
component: RegistrationStartComponent,
},
{
path: "",
component: RegistrationStartSecondaryComponent,
outlet: "secondary",
data: {
loginRoute: "/home",
} satisfies RegistrationStartSecondaryComponentData,
},
],
},
{
path: "finish-signup",
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
data: {
pageTitle: "setAStrongPassword",
pageSubtitle: "finishCreatingYourAccountBySettingAPassword",
} satisfies AnonLayoutWrapperData,
children: [
{
path: "",
component: RegistrationFinishComponent,
},
],
},
],
},
...extensionRefreshSwap(AboutPageComponent, AboutPageV2Component, {
path: "about",
canActivate: [AuthGuard],

View File

@ -57,12 +57,12 @@
>
<div class="vault-select-org-text-container">
<i
*ngIf="organization.planProductType !== 1"
*ngIf="organization.productTierType !== 1"
class="bwi bwi-fw bwi-business vault-select-prefix-icon"
aria-hidden="true"
></i>
<i
*ngIf="organization.planProductType === 1"
*ngIf="organization.productTierType === 1"
class="bwi bwi-fw bwi-family vault-select-prefix-icon"
aria-hidden="true"
></i>

View File

@ -5,7 +5,7 @@ import { BehaviorSubject } from "rxjs";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { ProductType } from "@bitwarden/common/enums";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { ObservableTracker } from "@bitwarden/common/spec";
import { CipherId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@ -78,7 +78,7 @@ describe("VaultPopupItemsService", () => {
mockOrg = {
id: "org1",
name: "Organization 1",
planProductType: ProductType.Enterprise,
productTierType: ProductTierType.Enterprise,
} as Organization;
mockCollections = [

View File

@ -6,7 +6,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { ProductType } from "@bitwarden/common/enums";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
@ -206,7 +206,7 @@ describe("VaultPopupListFiltersService", () => {
name: "family org",
id: "1234-3323-23223",
enabled: true,
planProductType: ProductType.Families,
productTierType: ProductTierType.Families,
},
] as Organization[];
@ -224,7 +224,7 @@ describe("VaultPopupListFiltersService", () => {
name: "free org",
id: "1234-3323-23223",
enabled: true,
planProductType: ProductType.Free,
productTierType: ProductTierType.Free,
},
] as Organization[];
@ -242,7 +242,7 @@ describe("VaultPopupListFiltersService", () => {
name: "free org",
id: "1234-3323-23223",
enabled: false,
planProductType: ProductType.Free,
productTierType: ProductTierType.Free,
},
] as Organization[];

View File

@ -16,7 +16,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { ProductType } from "@bitwarden/common/enums";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@ -216,8 +216,8 @@ export class VaultPopupListFiltersService {
// Show a warning icon if the organization is deactivated
icon = "bwi-exclamation-triangle tw-text-danger";
} else if (
org.planProductType === ProductType.Families ||
org.planProductType === ProductType.Free
org.productTierType === ProductTierType.Families ||
org.productTierType === ProductTierType.Free
) {
// Show a family icon if the organization is a family or free org
icon = "bwi-family";

View File

@ -1,5 +1,5 @@
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { ProductType } from "@bitwarden/common/enums";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
@ -26,13 +26,13 @@ export class PopupCipherView extends CipherView {
* Get the bwi icon for the cipher according to the organization type.
*/
get orgIcon(): "bwi-family" | "bwi-business" | null {
switch (this.organization?.planProductType) {
case ProductType.Free:
case ProductType.Families:
switch (this.organization?.productTierType) {
case ProductTierType.Free:
case ProductTierType.Families:
return "bwi-family";
case ProductType.Teams:
case ProductType.Enterprise:
case ProductType.TeamsStarter:
case ProductTierType.Teams:
case ProductTierType.Enterprise:
case ProductTierType.TeamsStarter:
return "bwi-business";
default:
return null;

View File

@ -118,7 +118,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Name" xml:space="preserve">
<value>Bitwarden Password Manager</value>
<value>Bitwarden 비밀번호 관리자</value>
</data>
<data name="Summary" xml:space="preserve">
<value>집에서도, 직장에서도, 이동 중에도 Bitwarden은 비밀번호, 패스키, 민감 정보를 쉽게 보호합니다.</value>

View File

@ -124,48 +124,48 @@
<value>Вдома, на роботі чи в дорозі, Bitwarden захищає ваші паролі, ключі доступу та конфіденційну інформацію.</value>
</data>
<data name="Description" xml:space="preserve">
<value>Визнаний найкращим менеджером паролів за версією PCMag, WIRED, The Verge, CNET, G2 та інших!
<value>Визнаний найкращим менеджером паролів виданнями PCMag, WIRED, The Verge, CNET, G2 та іншими!
ЗАХИСТІТЬ СВОЄ ЦИФРОВЕ ЖИТТЯ
Убезпечте своє цифрове життя та захистіться від витоку даних, створивши та зберігши унікальні, надійні паролі для кожного облікового запису. Зберігайте всі дані в наскрізному зашифрованому сховищі паролів, доступ до якого маєте лише ви.
Убезпечте своє цифрове життя та захистіться від витоків даних, генеруючи та зберігаючи унікальні надійні паролі для кожного облікового запису. Зберігайте все в наскрізно зашифрованому сховищі паролів, доступ до якого маєте тільки ви.
ОТРИМУВАТИ ДОСТУП ДО СВОЇХ ДАНИХ БУДЬ-ДЕ, БУДЬ-КОЛИ, БУДЬ НА ЯКОМУ ПРИСТРОЇ
Легко керуйте, зберігайте, захищайте та діліться необмеженою кількістю паролів на необмеженій кількості пристроїв без обмежень.
ДОСТУП ДО ДАНИХ БУДЬ-ДЕ, БУДЬ-КОЛИ, НА БУДЬ-ЯКОМУ ПРИСТРОЇ
Легко керуйте, зберігайте, захищайте та діліться необмеженою кількістю паролів на необмеженій кількості пристроїв.
КОЖЕН ПОВИНЕН МАТИ ІНСТРУМЕНТИ, ЩОБ ЗАЛИШАТИСЯ В БЕЗПЕЦІ В ІНТЕРНЕТІ
Користуйтеся Bitwarden безкоштовно, без реклами і без продажу даних. Bitwarden вважає, що кожен повинен мати можливість залишатися в безпеці в Інтернеті. Преміум-плани пропонують доступ до розширених функцій.
КОЖЕН ПОВИНЕН МАТИ ІНСТРУМЕНТИ ДЛЯ БЕЗПЕКИ В ІНТЕРНЕТІ
Використовуйте Bitwarden безплатно без реклами або продажу даних. Bitwarden вважає, що кожен повинен мати можливість залишатися в безпеці в Інтернеті. Завдяки тарифним планам Преміум можна отримати доступ до розширених можливостей.
РОЗШИРЮЙТЕ МОЖЛИВОСТІ СВОЇХ КОМАНД ЗА ДОПОМОГОЮ BITWARDEN
Плани для Команд та Підприємства містять професійні бізнес-функції. Деякі приклади включають інтеграцію SSO, самостійний хостинг, інтеграцію каталогів і забезпечення SCIM, глобальні політики, доступ до API, журнали подій і багато іншого.
РОЗШИРТЕ МОЖЛИВОСТІ СВОЇХ КОМАНД ЗА ДОПОМОГОЮ BITWARDEN
Плани для команд і компаній мають професійні бізнес-функції. Вони передбачають інтеграцію єдиного входу (SSO), власне розміщення, інтеграцію каталогів та забезпечення SCIM, глобальні політики, доступ до API, журнали подій тощо.
Використовуйте Bitwarden, щоб захистити своїх співробітників і ділитися конфіденційною інформацією з колегами.
Використовуйте Bitwarden для захисту персоналу та обміну конфіденційною інформацією з колегами.
Більше причин вибрати Bitwarden:
Інші причини для вибору Bitwarden:
Шифрування світового класу
Паролі захищені вдосконаленим наскрізним шифруванням (біт AES-256, солоний хештег і PBKDF2 SHA-256), тому ваші дані залишаються надійно захищеними та конфіденційними.
Паролі захищаються розширеним наскрізним шифруванням (AES-256, сіллю хешування і PBKDF2 SHA-256), тому ваші дані завжди зберігаються приватно і в безпеці.
Аудит третьої сторони
Bitwarden регулярно проводить комплексні сторонні аудити безпеки з відомими компаніями, що займаються безпекою. Ці щорічні аудити включають оцінку вихідного коду та тестування на проникнення на всіх IP-адресах, серверах і веб-додатках Bitwarden.
Сторонні аудити
Bitwarden регулярно проводить комплексні аудити безпеки із залученням третіх сторін відомих компаній у сфері безпеки. Під час цих щорічних аудитів проводиться оцінка програмного коду і тестування на проникнення через IP-адреси Bitwarden, сервери та вебпрограми.
Розширений 2FA
Захистіть свій вхід за допомогою стороннього автентифікатора, кодів, надісланих електронною поштою, або облікових даних FIDO2 WebAuthn, наприклад, апаратного ключа безпеки або пароля.
Розширена 2FA
Захистіть свої дані входу за допомогою стороннього автентифікатора, кодів, що надсилаються електронною поштою, або облікових даних FIDO2 WebAuthn, як-от апаратний ключ безпеки або ключ доступу.
Bitwarden Send
Передавайте дані безпосередньо іншим, зберігаючи при цьому наскрізну зашифровану безпеку та обмежуючи вразливість.
Відправлення Bitwarden
Передавайте дані безпосередньо іншим користувачам, зберігаючи наскрізне шифрування та обмежуючи їх викриття.
Вбудований генератор
Створюйте довгі, складні та відмінні паролі та унікальні імена користувачів для кожного сайту, який ви відвідуєте. Інтеграція з провайдерами псевдонімів електронної пошти для додаткової конфіденційності.
Створюйте довгі, складні та чіткі паролі, а також унікальні імена користувачів для кожного сайту, який ви відвідуєте. Користуйтеся інтеграцією з провайдерами псевдонімів електронної пошти для забезпечення додаткової приватності.
Глобальні переклади
Переклади Bitwarden існують для більш ніж 60 мов, перекладені світовою спільнотою за допомогою Crowdin.
Переклад різними мовами
Bitwarden перекладено понад 60 мовами завдяки зусиллям нашої світової спільноти на Crowdin.
Крос-платформні додатки
Захищайте конфіденційні дані у своєму сховищі Bitwarden Vault та діліться ними з будь-якого браузера, мобільного пристрою, настільної операційної системи тощо.
Програми для різних платформ
Захищайте та діліться конфіденційними даними в межах свого сховища Bitwarden з будь-якого браузера, мобільного пристрою або комп'ютерної ОС, а також інших можливостей.
Bitwarden захищає більше, ніж просто паролі
Наскрізні рішення для управління зашифрованими обліковими даними від Bitwarden дозволяють організаціям захистити все, включаючи секрети розробників і досвід роботи з ключами. Відвідайте Bitwarden.com, щоб дізнатися більше про Bitwarden Secrets Manager і Bitwarden Passwordless.dev!
Bitwarden захищає не лише паролі
Комплексні рішення для керування наскрізно зашифрованими обліковими даними від Bitwarden дають змогу організаціям захищати все, включно з секретами розробників та ключами доступу. Відвідайте Bitwarden.com, щоб дізнатися більше про Менеджер секретів Bitwarden і Bitwarden Passwordless.dev!
</value>
</data>
<data name="AssetTitle" xml:space="preserve">

View File

@ -4,6 +4,7 @@ const config = require("../../libs/components/tailwind.config.base");
config.content = [
"./src/**/*.{html,ts}",
"../../libs/components/src/**/*.{html,ts}",
"../../libs/auth/src/**/*.{html,ts}",
"../../libs/angular/src/**/*.{html,ts}",
];

View File

@ -1,19 +1,18 @@
import { firstValueFrom, map } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { HashPurpose } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
import { MasterKey } from "@bitwarden/common/types/key";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { ConvertToKeyConnectorCommand } from "../../commands/convert-to-key-connector.command";
@ -26,16 +25,14 @@ export class UnlockCommand {
private accountService: AccountService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private cryptoService: CryptoService,
private stateService: StateService,
private userVerificationService: UserVerificationService,
private cryptoFunctionService: CryptoFunctionService,
private apiService: ApiService,
private logService: ConsoleLogService,
private keyConnectorService: KeyConnectorService,
private environmentService: EnvironmentService,
private syncService: SyncService,
private organizationApiService: OrganizationApiServiceAbstraction,
private logout: () => Promise<void>,
private kdfConfigService: KdfConfigService,
) {}
async run(password: string, cmdOptions: Record<string, any>) {
@ -52,62 +49,43 @@ export class UnlockCommand {
const [userId, email] = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])),
);
const kdfConfig = await this.kdfConfigService.getKdfConfig();
const masterKey = await this.cryptoService.makeMasterKey(password, email, kdfConfig);
const storedMasterKeyHash = await firstValueFrom(
this.masterPasswordService.masterKeyHash$(userId),
);
let passwordValid = false;
if (masterKey != null) {
if (storedMasterKeyHash != null) {
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(password, masterKey);
} else {
const serverKeyHash = await this.cryptoService.hashMasterKey(
password,
masterKey,
HashPurpose.ServerAuthorization,
);
const request = new SecretVerificationRequest();
request.masterPasswordHash = serverKeyHash;
try {
await this.apiService.postAccountVerifyPassword(request);
passwordValid = true;
const localKeyHash = await this.cryptoService.hashMasterKey(
password,
masterKey,
HashPurpose.LocalAuthorization,
);
await this.masterPasswordService.setMasterKeyHash(localKeyHash, userId);
} catch {
// Ignore
}
const verification = {
type: VerificationType.MasterPassword,
secret: password,
} as MasterPasswordVerification;
let masterKey: MasterKey;
try {
const response = await this.userVerificationService.verifyUserByMasterPassword(
verification,
userId,
email,
);
masterKey = response.masterKey;
} catch (e) {
// verification failure throws
return Response.error(e.message);
}
const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey);
if (await this.keyConnectorService.getConvertAccountRequired()) {
const convertToKeyConnectorCommand = new ConvertToKeyConnectorCommand(
this.keyConnectorService,
this.environmentService,
this.syncService,
this.organizationApiService,
this.logout,
);
const convertResponse = await convertToKeyConnectorCommand.run();
if (!convertResponse.success) {
return convertResponse;
}
}
if (passwordValid) {
await this.masterPasswordService.setMasterKey(masterKey, userId);
const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey);
if (await this.keyConnectorService.getConvertAccountRequired()) {
const convertToKeyConnectorCommand = new ConvertToKeyConnectorCommand(
this.keyConnectorService,
this.environmentService,
this.syncService,
this.organizationApiService,
this.logout,
);
const convertResponse = await convertToKeyConnectorCommand.run();
if (!convertResponse.success) {
return convertResponse;
}
}
return this.successResponse();
} else {
return Response.error("Invalid master password.");
}
return this.successResponse();
}
private async setNewSessionKey() {

View File

@ -140,16 +140,14 @@ export abstract class BaseProgram {
this.serviceContainer.accountService,
this.serviceContainer.masterPasswordService,
this.serviceContainer.cryptoService,
this.serviceContainer.stateService,
this.serviceContainer.userVerificationService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.apiService,
this.serviceContainer.logService,
this.serviceContainer.keyConnectorService,
this.serviceContainer.environmentService,
this.serviceContainer.syncService,
this.serviceContainer.organizationApiService,
this.serviceContainer.logout,
this.serviceContainer.kdfConfigService,
);
const response = await command.run(null, null);
if (!response.success) {

View File

@ -468,7 +468,7 @@ export class GetCommand extends DownloadCommand {
if (Utils.isGuid(id)) {
org = await this.organizationService.getFromState(id);
} else if (id.trim() !== "") {
let orgs = await this.organizationService.getAll();
let orgs = await firstValueFrom(this.organizationService.organizations$);
orgs = CliUtils.searchOrganizations(orgs, id);
if (orgs.length > 1) {
return Response.multipleResults(orgs.map((c) => c.id));

View File

@ -1,3 +1,5 @@
import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
@ -239,7 +241,7 @@ export class ListCommand {
}
private async listOrganizations(options: Options) {
let organizations = await this.organizationService.getAll();
let organizations = await firstValueFrom(this.organizationService.memberOrganizations$);
if (options.search != null && options.search.trim() !== "") {
organizations = CliUtils.searchOrganizations(organizations, options.search);

View File

@ -120,16 +120,14 @@ export class OssServeConfigurator {
this.serviceContainer.accountService,
this.serviceContainer.masterPasswordService,
this.serviceContainer.cryptoService,
this.serviceContainer.stateService,
this.serviceContainer.userVerificationService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.apiService,
this.serviceContainer.logService,
this.serviceContainer.keyConnectorService,
this.serviceContainer.environmentService,
this.serviceContainer.syncService,
this.serviceContainer.organizationApiService,
async () => await this.serviceContainer.logout(),
this.serviceContainer.kdfConfigService,
);
this.sendCreateCommand = new SendCreateCommand(

View File

@ -270,16 +270,14 @@ export class Program extends BaseProgram {
this.serviceContainer.accountService,
this.serviceContainer.masterPasswordService,
this.serviceContainer.cryptoService,
this.serviceContainer.stateService,
this.serviceContainer.userVerificationService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.apiService,
this.serviceContainer.logService,
this.serviceContainer.keyConnectorService,
this.serviceContainer.environmentService,
this.serviceContainer.syncService,
this.serviceContainer.organizationApiService,
async () => await this.serviceContainer.logout(),
this.serviceContainer.kdfConfigService,
);
const response = await command.run(password, cmd);
this.processResponse(response);

View File

@ -613,7 +613,6 @@ export class ServiceContainer {
await this.cryptoService.clearStoredUserKey(KeySuffixOptions.Auto);
this.userVerificationService = new UserVerificationService(
this.stateService,
this.cryptoService,
this.accountService,
this.masterPasswordService,

View File

@ -24,7 +24,7 @@
"**/node_modules/argon2/package.json",
"**/node_modules/argon2/lib/binding/napi-v3/argon2.node"
],
"electronVersion": "29.4.2",
"electronVersion": "30.1.0",
"generateUpdatesFilesForAllChannels": true,
"publish": {
"provider": "generic",

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