diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index 07986ed27e..42acd62420 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -221,7 +221,7 @@ jobs: token: '${{ secrets.GITHUB_TOKEN }}' initial-status: 'in_progress' environment-url: http://vault.bitwarden.com - environment: 'Web Vault - Production' + environment: 'Web Vault - US Production Cloud' description: 'Deployment ${{ needs.setup.outputs.release_version }} from branch ${{ github.ref_name }}' task: release diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 92bc8fb16e..134c045f78 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -494,10 +494,10 @@ "message": "تم إنشاء حسابك الجديد! يمكنك الآن تسجيل الدخول." }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "سجلتَ الدخول بنجاح" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "يمكنك إغلاق هذه النافذة" }, "masterPassSent": { "message": "لقد أرسلنا لك رسالة بريد إلكتروني تحتوي على تلميح كلمة المرور الرئيسية." @@ -871,7 +871,7 @@ "message": "خيارات تسجيل الدخول بخطوتين المملوكة لجهات اخرى مثل YubiKey و Duo." }, "ppremiumSignUpReports": { - "message": "نظافة كلمة المرور، صحة الحساب، وتقارير خرق البيانات للحفاظ على سلامة خزنتك." + "message": "نظافة كلمة المرور، صحة الحساب، وتقارير تسريبات البيانات للحفاظ على سلامة خزنتك." }, "ppremiumSignUpTotp": { "message": "مورد رمز التحقق (2FA) لتسجيل الدخول في خزنتك." @@ -1367,7 +1367,7 @@ "message": "تحقق مما إذا تم الكشف عن كلمة المرور." }, "passwordExposed": { - "message": "تم الكشف عن كلمة المرور هذه $VALUE$ مرّة (ات) في خروقات البيانات. يجب عليك تغييرها.", + "message": "تم الكشف عن كلمة المرور هذه $VALUE$ مرّة(ات) في تسريبات البيانات. يجب عليك تغييرها.", "placeholders": { "value": { "content": "$1", @@ -1376,7 +1376,7 @@ } }, "passwordSafe": { - "message": "لم يتم العثور على كلمة المرور هذه في أي عمليات اختراق معروفة للبيانات. من المفترض أن تكون آمنة للاستخدام." + "message": "لم يتم العثور على كلمة المرور هذه في أي عمليات تسريبات للبيانات معروفة. من المفترض أن تكون آمنة للاستخدام." }, "baseDomain": { "message": "النطاق الأساسي", @@ -1712,22 +1712,22 @@ "message": "فشل القياسات الحيوية" }, "biometricsFailedDesc": { - "message": "Biometrics cannot be completed, consider using a master password or logging out. If this persists, please contact Bitwarden support." + "message": "لا يمكن إكمال القياسات الحيوية، فكر في استخدام كلمة مرور رئيسية أو تسجيل الخروج. إذا استمر ذلك، يرجى الاتصال بدعم Bitwarden." }, "nativeMessaginPermissionErrorTitle": { - "message": "Permission not provided" + "message": "الإذن غير متوفر" }, "nativeMessaginPermissionErrorDesc": { - "message": "Without permission to communicate with the Bitwarden Desktop Application we cannot provide biometrics in the browser extension. Please try again." + "message": "بدون إذن للتواصل مع تطبيق سطح المكتب Bitwarden لا يمكننا توفير القياسات الحيوية في ملحق المتصفح. الرجاء المحاولة مرة أخرى." }, "nativeMessaginPermissionSidebarTitle": { - "message": "Permission request error" + "message": "خطأ في طلب الإذن" }, "nativeMessaginPermissionSidebarDesc": { - "message": "This action cannot be done in the sidebar, please retry the action in the popup or popout." + "message": "لا يمكن القيام بهذا الإجراء في الشريط الجانبي، يرجى إعادة محاولة الإجراء في النافذة المنبثقة أو المنبثقة." }, "personalOwnershipSubmitError": { - "message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available collections." + "message": "بسبب سياسة المؤسسة، يمنع عليك حفظ العناصر في خزانتك الشخصية. غيّر خيار الملكية إلى مؤسسة واختر من المجموعات المتاحة." }, "personalOwnershipPolicyInEffect": { "message": "An organization policy is affecting your ownership options." @@ -1766,10 +1766,10 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { - "message": "Text" + "message": "نص" }, "sendTypeFile": { - "message": "File" + "message": "ملف" }, "allSends": { "message": "All Sends", @@ -1810,10 +1810,10 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disabled": { - "message": "Disabled" + "message": "معطّل" }, "removePasswordConfirmation": { - "message": "Are you sure you want to remove the password?" + "message": "هل أنت متأكد من أنك تريد إزالة كلمة المرور؟" }, "deleteSend": { "message": "Delete Send", @@ -1839,7 +1839,7 @@ "message": "The file you want to send." }, "deletionDate": { - "message": "Deletion date" + "message": "تاريخ الحذف" }, "deletionDateDesc": { "message": "The Send will be permanently deleted on the specified date and time.", @@ -1933,10 +1933,10 @@ "message": "In order to choose a file using Safari, pop out to a new window by clicking this banner." }, "sendFileCalloutHeader": { - "message": "Before you start" + "message": "قبل أن تبدأ" }, "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", + "message": "لاستخدام منتقي التاريخ على نمط التقويم", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" }, "sendFirefoxCustomDatePopoutMessage2": { @@ -1944,14 +1944,14 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" }, "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", + "message": "أن يخرج من النافذة.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" }, "expirationDateIsInvalid": { - "message": "The expiration date provided is not valid." + "message": "صلاحية تاريخ الانتهاء المقدّم غير صحيح." }, "deletionDateIsInvalid": { - "message": "The deletion date provided is not valid." + "message": "صلاحية تاريخ الحذف المقدّم غير صحيح." }, "expirationDateAndTimeRequired": { "message": "An expiration date and time are required." @@ -2017,7 +2017,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": { @@ -2251,55 +2251,55 @@ "message": "New around here?" }, "rememberEmail": { - "message": "Remember email" + "message": "تذكر البريد الإلكتروني" }, "loginWithDevice": { - "message": "Log in with device" + "message": "تسجيل الدخول بالجهاز" }, "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" + "message": "يجب إعداد تسجيل الدخول بالجهاز في إعدادات تطبيق Bitwarden. هل تحتاج إلى خِيار آخر؟" }, "fingerprintPhraseHeader": { - "message": "Fingerprint phrase" + "message": "عبارة بصمة الإصبع" }, "fingerprintMatchInfo": { - "message": "Please make sure your vault is unlocked and the Fingerprint phrase matches on the other device." + "message": "الرجاء التأكد من أن الخزنة الخاصة بك غير مقفلة وأن عبارة بصمة الإصبع تتطابق على الجهاز الآخر." }, "resendNotification": { - "message": "Resend notification" + "message": "إعادة إرسال الإشعار" }, "viewAllLoginOptions": { - "message": "View all log in options" + "message": "عرض جميع خيارات تسجيل الدخول" }, "notificationSentDevice": { - "message": "A notification has been sent to your device." + "message": "تم إرسال إشعار إلى جهازك." }, "loginInitiated": { - "message": "Login initiated" + "message": "بَدْء تسجيل الدخول" }, "exposedMasterPassword": { - "message": "Exposed Master Password" + "message": "كلمة المرور الرئيسية مكشوفة" }, "exposedMasterPasswordDesc": { - "message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?" + "message": "كلمة المرور موجودة في تسريبات البيانات. استخدم كلمة مرور فريدة لحماية حسابك. هل أنت متأكد من أنك تريد استخدام كلمة مرور مكشوفة؟" }, "weakAndExposedMasterPassword": { - "message": "Weak and Exposed Master Password" + "message": "كلمة المرور الرئيسية ضعيفة ومكشوفة" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" + "message": "تم تحديد كلمة مرور ضعيفة و موجودة في تسريبات البيانات. استخدم كلمة مرور قوية وفريدة لحماية حسابك. هل أنت متأكد من أنك تريد استخدام كلمة المرور هذه؟" }, "checkForBreaches": { - "message": "Check known data breaches for this password" + "message": "تحقق من تسريبات البيانات المعروفة لكلمة المرور هذه" }, "important": { - "message": "Important:" + "message": "مهم:" }, "masterPasswordHint": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "لا يمكن استعادة كلمة المرور الرئيسية إذا نسيتها!" }, "characterMinimum": { - "message": "$LENGTH$ character minimum", + "message": "$LENGTH$ حرف أدنى", "placeholders": { "length": { "content": "$1", @@ -2308,13 +2308,13 @@ } }, "autofillPageLoadPolicyActivated": { - "message": "Your organization policies have turned on auto-fill on page load." + "message": "سياسات مؤسستك شغلت الملء التلقائي في تحميل الصفحة." }, "howToAutofill": { - "message": "How to auto-fill" + "message": "كيفية الملء التلقائي" }, "autofillSelectInfoWithCommand": { - "message": "Select an item from this screen, use the shortcut $COMMAND$, or explore other options in settings.", + "message": "حدد عنصر من هذه الشاشة، واستخدام الاختصار $COMMAND$، أو استكشاف خيارات أخرى في الإعدادات.", "placeholders": { "command": { "content": "$1", @@ -2326,13 +2326,13 @@ "message": "Select an item from this screen, or explore other options in settings." }, "gotIt": { - "message": "Got it" + "message": "فهمت" }, "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." @@ -2356,34 +2356,34 @@ } }, "loggingInOn": { - "message": "Logging in on" + "message": "جارٍ تسجيل الدخول على" }, "opensInANewWindow": { - "message": "Opens in a new window" + "message": "تُفتح في نافذة جديدة" }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "موافقة الجهاز مطلوبة. حدّد خيار الموافقة أدناه:" }, "rememberThisDevice": { - "message": "Remember this device" + "message": "تذكر هذا الجهاز" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "إلغاء تحديد عند استخدام جهاز عام" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "الموافقة من جهازك الآخر" }, "requestAdminApproval": { - "message": "Request admin approval" + "message": "طلب موافقة المدير" }, "approveWithMasterPassword": { - "message": "Approve with master password" + "message": "الموافقة بواسطة كلمة المرور الرئيسية" }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, "eu": { - "message": "EU", + "message": "الاتحاد الأوروبي", "description": "European Union" }, "usDomain": { @@ -2530,126 +2530,126 @@ "description": "Toggling an expand/collapse state." }, "filelessImport": { - "message": "Import your data to Bitwarden?", + "message": "استيراد بياناتك إلى Bitwarden؟", "description": "Default notification title for triggering a fileless import." }, "lpFilelessImport": { - "message": "Protect your LastPass data and import to Bitwarden?", + "message": "حماية بياناتك الأخيرة واستيرادك إلى Bitwarden؟", "description": "LastPass specific notification title for triggering a fileless import." }, "lpCancelFilelessImport": { - "message": "Save as unencrypted file", + "message": "حفظ كملف غير مشفر", "description": "LastPass specific notification button text for cancelling a fileless import." }, "startFilelessImport": { - "message": "Import to Bitwarden", + "message": "استيراد إلى Bitwarden", "description": "Notification button text for starting a fileless import." }, "importing": { - "message": "Importing...", + "message": "جارِ الاستيراد...", "description": "Notification message for when an import is in progress." }, "dataSuccessfullyImported": { - "message": "Data successfully imported!", + "message": "لقد تم استيراد البيانات بنجاح!", "description": "Notification message for when an import has completed successfully." }, "dataImportFailed": { - "message": "Error importing. Check console for details.", + "message": "خطأ في الاستيراد. تحقق من وحدة التحكم للحصول على التفاصيل.", "description": "Notification message for when an import has failed." }, "importNetworkError": { - "message": "Network error encountered during import.", + "message": "تم مواجهة خطأ في الشبكة أثناء الاستيراد.", "description": "Notification message for when an import has failed due to a network error." }, "aliasDomain": { - "message": "Alias domain" + "message": "الاسم البديل للنطاق" }, "passwordRepromptDisabledAutofillOnPageLoad": { - "message": "Items with master password re-prompt cannot be auto-filled on page load. Auto-fill on page load turned off.", + "message": "العناصر مع إعادة طلب كلمة المرور الرئيسية لا يمكن ملئها تلقائيًا عند تحميل الصفحة. التعبئة التلقائية عند تحميل الصفحة.", "description": "Toast message for describing that master password re-prompt cannot be auto-filled on page load." }, "autofillOnPageLoadSetToDefault": { - "message": "Auto-fill on page load set to use default setting.", + "message": "ملء تلقائي عند تعيين تحميل الصفحة لاستخدام الإعداد الافتراضي.", "description": "Toast message for informing the user that auto-fill on page load has been set to the default setting." }, "turnOffMasterPasswordPromptToEditField": { - "message": "Turn off master password re-prompt to edit this field", + "message": "إيقاف تشغيل كلمة المرور الرئيسية مرة أخرى لتحرير هذا الحقل", "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, "skipToContent": { - "message": "Skip to content" + "message": "تخطي إلى المحتوى" }, "bitwardenOverlayButton": { - "message": "Bitwarden auto-fill menu button", + "message": "زر قائمة الملء التلقائي Bitwarden", "description": "Page title for the iframe containing the overlay button" }, "toggleBitwardenVaultOverlay": { - "message": "Toggle Bitwarden auto-fill menu", + "message": "تبديل قائمة الملء التلقائي لـ Bitwarden", "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Bitwarden auto-fill menu", + "message": "قائمة الملء التلقائي Bitwarden", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { - "message": "Unlock your account to view matching logins", + "message": "افتح حسابك لعرض تسجيلات الدخول المطابقة", "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": { - "message": "Fill credentials for", + "message": "ملء بيانات الاعتماد لـ", "description": "Screen reader text for when overlay item is in focused" }, "partialUsername": { - "message": "Partial username", + "message": "اسم المستخدم الجزئي", "description": "Screen reader text for when a login item is focused where a partial username is displayed. SR will announce this phrase before reading the text of the partial username" }, "noItemsToShow": { - "message": "No items to show", + "message": "لا توجد عناصر لإظهارها", "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": { - "message": "Add new vault item", + "message": "إضافة عنصر مخزن جديد", "description": "Screen reader text (aria-label) for new item button in overlay" }, "bitwardenOverlayMenuAvailable": { - "message": "Bitwarden auto-fill menu available. Press the down arrow key to select.", + "message": "تتوفر قائمة الملء التلقائي لBitwarden. اضغط على مفتاح السهم لأسفل للتحديد.", "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { - "message": "Turn on" + "message": "فعّل" }, "ignore": { - "message": "Ignore" + "message": "تجاهل" }, "importData": { - "message": "Import data", + "message": "استيراد البيانات", "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { - "message": "Import error" + "message": "خطأ في الاستيراد" }, "importErrorDesc": { - "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + "message": "حدثت مشكلة في البيانات التي حاولت استيرادها. الرجاء حل الأخطاء المدرجة أدناه في الملف المصدر الخاص بك وحاول مرة أخرى." }, "resolveTheErrorsBelowAndTryAgain": { - "message": "Resolve the errors below and try again." + "message": "حِل الأخطاء أدناه ثم حاول مرة أخرى." }, "description": { - "message": "Description" + "message": "الوصف" }, "importSuccess": { - "message": "Data successfully imported" + "message": "تم استيراد البيانات بنجاح" }, "importSuccessNumberOfItems": { - "message": "A total of $AMOUNT$ items were imported.", + "message": "تم استيراد مجموع العناصر $AMOUNT$.", "placeholders": { "amount": { "content": "$1", @@ -2658,46 +2658,46 @@ } }, "tryAgain": { - "message": "Try again" + "message": "حاول مجددًا" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "التحقق مطلوب لهذا الإجراء. عيّن رمز PIN للمتابعة." }, "setPin": { - "message": "Set PIN" + "message": "عيّن PIN" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "التحقق بواسطة القياسات الحيوية" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "في انتظار تأكيد" }, "couldNotCompleteBiometrics": { - "message": "Could not complete biometrics." + "message": "لا يمكن إكمال القياسات الحيوية." }, "needADifferentMethod": { - "message": "Need a different method?" + "message": "هل تحتاج إلى طريقة مختلفة؟" }, "useMasterPassword": { - "message": "Use master password" + "message": "استخدام كلمة المرور الرئيسية" }, "usePin": { - "message": "Use PIN" + "message": "استخدم PIN" }, "useBiometrics": { - "message": "Use biometrics" + "message": "استخدم السياقات الحيوية" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "أدخِل رمز التحقق الذي أرسل إلى بريدك الإلكتروني." }, "resendCode": { - "message": "Resend code" + "message": "أعِد إرسال الرمز" }, "total": { - "message": "Total" + "message": "المجموع" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "أنت تستورد البيانات إلى $ORGANIZATION$. قد يتم مشاركة بياناتك مع أعضاء هذه المؤسسة. هل تريد المتابعة؟", "placeholders": { "organization": { "content": "$1", @@ -2706,46 +2706,46 @@ } }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "قم بتشغيل Duo واتبع الخطوات لإنهاء تسجيل الدخول." }, "duoRequiredForAccount": { "message": "تسجيل الدخول لـ Duo من خطوتين مطلوب لحسابك." }, "popoutTheExtensionToCompleteLogin": { - "message": "Popout the extension to complete login." + "message": "انبثق الامتداد لإكمال تسجيل الدخول." }, "popoutExtension": { - "message": "Popout extension" + "message": "تمديد منبثق" }, "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." + "message": "خطأ في فك تشفير الملف المصدر. مفتاح التشفير الخاص بك لا يتطابق مع مفتاح التشفير المستخدم عند تصدير البيانات." }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "كلمة مرور الملف غير صالحة، الرجاء استخدام كلمة المرور التي أدخلتها عند إنشاء ملف التصدير." }, "importDestination": { - "message": "Import destination" + "message": "وجهة الاستيراد" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "تعرف على خيارات الاستيراد الخاصة بك" }, "selectImportFolder": { - "message": "Select a folder" + "message": "اختر مجلدًا" }, "selectImportCollection": { - "message": "Select a collection" + "message": "اختر مجموعة" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "حدد هذا الخيار إذا كنت تريد نقل محتويات الملف المستورد إلى $DESTINATION$", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -2755,25 +2755,25 @@ } }, "importUnassignedItemsError": { - "message": "File contains unassigned items." + "message": "الملف يحتوي على عناصر غير مسندة." }, "selectFormat": { - "message": "Select the format of the import file" + "message": "حدد تنسيق ملف الاستيراد" }, "selectImportFile": { - "message": "Select the import file" + "message": "حدد ملف الاستيراد" }, "chooseFile": { - "message": "Choose File" + "message": "اختر ملف" }, "noFileChosen": { - "message": "No file chosen" + "message": "لم يتم اختيار ملف" }, "orCopyPasteFileContents": { - "message": "or copy/paste the import file contents" + "message": "أو انسخ/الصق محتويات ملف الاستيراد" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "تعليمات $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -2783,19 +2783,19 @@ } }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "تأكيد تصدير الخزنة" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "هذا الملف محمي بكلمة مرور. الرجاء إدخال كلمة مرور الملف لاستيراد البيانات." }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "تأكيد كلمة مرور الملف" }, "typePasskey": { "message": "Passkey" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "لن يتم نسخ Passkey" }, "passkeyNotCopiedAlert": { "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" @@ -2816,7 +2816,7 @@ "message": "You do not have a matching login for this site." }, "confirm": { - "message": "Confirm" + "message": "تأكيد" }, "savePasskey": { "message": "Save passkey" @@ -2852,13 +2852,13 @@ "message": "Incorrect username or password" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "كلمة مرور غير صحيحة" }, "incorrectCode": { - "message": "Incorrect code" + "message": "رمز غير صحيح" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "رمز PIN غير صحيح" }, "multifactorAuthenticationFailed": { "message": "Multifactor authentication failed" @@ -2867,10 +2867,10 @@ "message": "Include shared folders" }, "lastPassEmail": { - "message": "LastPass Email" + "message": "بريد LastPass الإلكتروني" }, "importingYourAccount": { - "message": "Importing your account..." + "message": "جارِ استيراد حسابك..." }, "lastPassMFARequired": { "message": "LastPass multifactor authentication required" diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 81f549deda..fdd8d04945 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -2709,7 +2709,7 @@ "message": "Inicie o Duo e siga os passos para concluir o início de sessão." }, "duoRequiredForAccount": { - "message": "A verificação em dois passos Duo é necessária para a sua conta." + "message": "A verificação de dois passos Duo é necessária para a sua conta." }, "popoutTheExtensionToCompleteLogin": { "message": "Abra a extensão para concluir o início de sessão." diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index fb840cd758..ab4f07c5cb 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -2709,10 +2709,10 @@ "message": "Покренути DUO и пратите кораке да бисте завршили пријављивање." }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "Duo пријава у два корака је потребна за ваш налог." }, "popoutTheExtensionToCompleteLogin": { - "message": "Popout the extension to complete login." + "message": "Искачући додатак да бисте довршили пријаву." }, "popoutExtension": { "message": "Popout extension" @@ -2995,15 +2995,15 @@ "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "Акредитиви су успешно сачувани!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "Акредитиви су успешно ажурирани!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { - "message": "Error saving credentials. Check console for details.", + "message": "Грешка при чувању акредитива. Проверите конзолу за детаље.", "description": "Notification message for when saving credentials has failed." } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 686309775d..dd93a93fc9 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -2709,7 +2709,7 @@ "message": "启动 DUO 并按照步骤完成登录。" }, "duoRequiredForAccount": { - "message": "您的账户需要 Duo 两步登录。" + "message": "您的账户要求使用 Duo 两步登录。" }, "popoutTheExtensionToCompleteLogin": { "message": "弹出扩展以完成登录。" @@ -2971,7 +2971,7 @@ "description": "Label indicating the most common import formats" }, "overrideDefaultBrowserAutofillTitle": { - "message": "将 Bitwarden 设置为您的默认密码管理器?", + "message": "将 Bitwarden 设置为您的默认密码管理器吗?", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { @@ -2979,7 +2979,7 @@ "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "将 Bitwarden 设置为您的默认密码管理器?", + "message": "将 Bitwarden 设置为您的默认密码管理器", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { @@ -2995,15 +2995,15 @@ "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "凭据保存成功!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "凭据更新成功!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { - "message": "Error saving credentials. Check console for details.", + "message": "保存凭据时出错。检查控制台以获取详细信息。", "description": "Notification message for when saving credentials has failed." } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index d8ccc1101e..80e05c7c1c 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -2658,7 +2658,7 @@ } }, "tryAgain": { - "message": "Try again" + "message": "再試一次" }, "verificationRequiredForActionSetPinToContinue": { "message": "Verification required for this action. Set a PIN to continue." diff --git a/apps/browser/src/autofill/background/abstractions/notification.background.ts b/apps/browser/src/autofill/background/abstractions/notification.background.ts index ba6d18edbc..94ab1a398d 100644 --- a/apps/browser/src/autofill/background/abstractions/notification.background.ts +++ b/apps/browser/src/autofill/background/abstractions/notification.background.ts @@ -109,6 +109,7 @@ type NotificationBackgroundExtensionMessageHandlers = { bgReopenUnlockPopout: ({ sender }: BackgroundSenderParam) => Promise; checkNotificationQueue: ({ sender }: BackgroundSenderParam) => Promise; collectPageDetailsResponse: ({ message }: BackgroundMessageParam) => Promise; + getWebVaultUrlForNotification: () => string; }; export { diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index af92dae9eb..29c8481e5f 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -1332,5 +1332,23 @@ describe("NotificationBackground", () => { expect(openUnlockWindowSpy).toHaveBeenCalled(); }); }); + + describe("getWebVaultUrlForNotification", () => { + it("returns the web vault url", async () => { + const message: NotificationBackgroundExtensionMessage = { + command: "getWebVaultUrlForNotification", + }; + const webVaultUrl = "https://example.com"; + const environmentServiceSpy = jest + .spyOn(environmentService, "getWebVaultUrl") + .mockReturnValueOnce(webVaultUrl); + + sendExtensionRuntimeMessage(message); + await flushPromises(); + + expect(environmentServiceSpy).toHaveBeenCalled(); + expect(environmentServiceSpy).toHaveReturnedWith(webVaultUrl); + }); + }); }); }); diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 42d8d47b23..8ebf1bd26e 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -6,7 +6,6 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { ThemeType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -58,6 +57,7 @@ export default class NotificationBackground { bgUnlockPopoutOpened: ({ message, sender }) => this.unlockVault(message, sender.tab), checkNotificationQueue: ({ sender }) => this.checkNotificationQueue(sender.tab), bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab), + getWebVaultUrlForNotification: () => this.getWebVaultUrl(), }; constructor( @@ -134,11 +134,9 @@ export default class NotificationBackground { notificationQueueMessage: NotificationQueueMessageItem, ) { const notificationType = notificationQueueMessage.type; - const webVaultURL = this.environmentService.getWebVaultUrl(); const typeData: Record = { isVaultLocked: notificationQueueMessage.wasVaultLocked, - theme: await this.getCurrentTheme(), - webVaultURL, + theme: await this.stateService.getTheme(), }; switch (notificationType) { @@ -158,18 +156,6 @@ export default class NotificationBackground { }); } - private async getCurrentTheme() { - const theme = await this.stateService.getTheme(); - - if (theme !== ThemeType.System) { - return theme; - } - - return window.matchMedia("(prefers-color-scheme: dark)").matches - ? ThemeType.Dark - : ThemeType.Light; - } - /** * Removes any login messages from the notification queue that * are associated with the specified tab. @@ -636,6 +622,10 @@ export default class NotificationBackground { return await firstValueFrom(this.folderService.folderViews$); } + private getWebVaultUrl(): string { + return this.environmentService.getWebVaultUrl(); + } + private async removeIndividualVault(): Promise { return await firstValueFrom( this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership), diff --git a/apps/browser/src/autofill/content/notification-bar.ts b/apps/browser/src/autofill/content/notification-bar.ts index ac20401da5..5766017ebe 100644 --- a/apps/browser/src/autofill/content/notification-bar.ts +++ b/apps/browser/src/autofill/content/notification-bar.ts @@ -4,6 +4,7 @@ import { } from "../background/abstractions/notification.background"; import AutofillField from "../models/autofill-field"; import { WatchedForm } from "../models/watched-form"; +import { NotificationBarIframeInitData } from "../notification/abstractions/notification-bar"; import { FormData } from "../services/abstractions/autofill.service"; import { GlobalSettings, UserSettings } from "../types"; import { getFromLocalStorage, setupExtensionDisconnectAction } from "../utils"; @@ -856,33 +857,36 @@ async function loadNotificationBar() { // Notification Bar Functions (open, close, height adjustment, etc.) function closeExistingAndOpenBar(type: string, typeData: any) { - const barQueryParams = { + const notificationBarInitData: NotificationBarIframeInitData = { type, isVaultLocked: typeData.isVaultLocked, theme: typeData.theme, removeIndividualVault: typeData.removeIndividualVault, - webVaultURL: typeData.webVaultURL, importType: typeData.importType, }; - const barQueryString = new URLSearchParams(barQueryParams).toString(); - const barPage = "notification/bar.html?" + barQueryString; + const notificationBarUrl = "notification/bar.html"; const frame = document.getElementById("bit-notification-bar-iframe") as HTMLIFrameElement; - if (frame != null && frame.src.indexOf(barPage) >= 0) { + if (frame != null && frame.src.indexOf(notificationBarUrl) >= 0) { return; } closeBar(false); - openBar(type, barPage); + openBar(type, notificationBarUrl, notificationBarInitData); } - function openBar(type: string, barPage: string) { + function openBar( + type: string, + barPage: string, + notificationBarInitData: NotificationBarIframeInitData, + ) { barType = type; if (document.body == null) { return; } + setupInitNotificationBarMessageListener(notificationBarInitData); const barPageUrl: string = chrome.runtime.getURL(barPage); notificationBarIframe = document.createElement("iframe"); @@ -901,7 +905,30 @@ async function loadNotificationBar() { document.body.appendChild(frameDiv); (notificationBarIframe.contentWindow.location as any) = barPageUrl; + } + function setupInitNotificationBarMessageListener(initData: NotificationBarIframeInitData) { + const handleInitNotificationBarMessage = (event: MessageEvent) => { + const { source, data } = event; + if ( + source !== notificationBarIframe.contentWindow || + data?.command !== "initNotificationBar" + ) { + return; + } + + notificationBarIframe.contentWindow.postMessage( + { command: "initNotificationBar", initData }, + "*", + ); + injectSpacer(); + window.removeEventListener("message", handleInitNotificationBarMessage); + }; + + window.addEventListener("message", handleInitNotificationBarMessage); + } + + function injectSpacer() { const spacer = document.createElement("div"); spacer.id = "bit-notification-bar-spacer"; spacer.style.cssText = "height: 42px;"; diff --git a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts index e2753893b1..268617c419 100644 --- a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts +++ b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts @@ -1,13 +1,26 @@ -import { SaveOrUpdateCipherResult } from "../../background/abstractions/notification.background"; +type NotificationBarIframeInitData = { + type?: string; + isVaultLocked?: boolean; + theme?: string; + removeIndividualVault?: boolean; + importType?: string; +}; type NotificationBarWindowMessage = { [key: string]: any; command: string; + error?: string; + initData?: NotificationBarIframeInitData; }; type NotificationBarWindowMessageHandlers = { [key: string]: CallableFunction; - saveCipherAttemptCompleted: ({ message }: { message: SaveOrUpdateCipherResult }) => void; + initNotificationBar: ({ message }: { message: NotificationBarWindowMessage }) => void; + saveCipherAttemptCompleted: ({ message }: { message: NotificationBarWindowMessage }) => void; }; -export { NotificationBarWindowMessage, NotificationBarWindowMessageHandlers }; +export { + NotificationBarIframeInitData, + NotificationBarWindowMessage, + NotificationBarWindowMessageHandlers, +}; diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 1c21f89198..5d28bf8397 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -1,37 +1,42 @@ +import { ThemeType } from "@bitwarden/common/platform/enums"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { FilelessImportPort, FilelessImportType } from "../../tools/enums/fileless-import.enums"; -import { - AdjustNotificationBarMessageData, - SaveOrUpdateCipherResult, -} from "../background/abstractions/notification.background"; +import { AdjustNotificationBarMessageData } from "../background/abstractions/notification.background"; import { NotificationBarWindowMessageHandlers, NotificationBarWindowMessage, + NotificationBarIframeInitData, } from "./abstractions/notification-bar"; require("./bar.scss"); const logService = new ConsoleLogService(false); +let notificationBarIframeInitData: NotificationBarIframeInitData = {}; let windowMessageOrigin: string; const notificationBarWindowMessageHandlers: NotificationBarWindowMessageHandlers = { + initNotificationBar: ({ message }) => initNotificationBar(message), saveCipherAttemptCompleted: ({ message }) => handleSaveCipherAttemptCompletedMessage(message), }; -document.addEventListener("DOMContentLoaded", () => { - // delay 50ms so that we get proper body dimensions - setTimeout(load, 50); -}); - +globalThis.addEventListener("load", load); function load() { setupWindowMessageListener(); + postMessageToParent({ command: "initNotificationBar" }); +} - const theme = getQueryVariable("theme"); - document.documentElement.classList.add("theme_" + theme); +function initNotificationBar(message: NotificationBarWindowMessage) { + const { initData } = message; + if (!initData) { + return; + } + + notificationBarIframeInitData = initData; + const { isVaultLocked } = notificationBarIframeInitData; + setNotificationBarTheme(); - const isVaultLocked = getQueryVariable("isVaultLocked") == "true"; (document.getElementById("logo") as HTMLImageElement).src = isVaultLocked ? chrome.runtime.getURL("images/icon38_locked.png") : chrome.runtime.getURL("images/icon38.png"); @@ -55,16 +60,7 @@ function load() { startFilelessImport: chrome.i18n.getMessage("startFilelessImport"), }; - const logoLink = document.getElementById("logo-link") as HTMLAnchorElement; - logoLink.title = i18n.appName; - - // Update logo link to user's regional domain - const webVaultURL = getQueryVariable("webVaultURL"); - const newVaultURL = webVaultURL && decodeURIComponent(webVaultURL); - - if (newVaultURL && newVaultURL !== logoLink.href) { - logoLink.href = newVaultURL; - } + setupLogoLink(i18n); // i18n for "Add" template const addTemplate = document.getElementById("template-add") as HTMLTemplateElement; @@ -106,7 +102,7 @@ function load() { unlockTemplate.content.getElementById("unlock-text").textContent = i18n.notificationUnlockDesc; // i18n for "Fileless Import" (fileless-import) template - const isLpImport = getQueryVariable("importType") === FilelessImportType.LP; + const isLpImport = initData.importType === FilelessImportType.LP; const importTemplate = document.getElementById("template-fileless-import") as HTMLTemplateElement; const startImportButton = importTemplate.content.getElementById("start-fileless-import"); @@ -125,13 +121,14 @@ function load() { const closeButton = document.getElementById("close-button"); closeButton.title = i18n.close; - if (getQueryVariable("type") === "add") { + const notificationType = initData.type; + if (initData.type === "add") { handleTypeAdd(); - } else if (getQueryVariable("type") === "change") { + } else if (notificationType === "change") { handleTypeChange(); - } else if (getQueryVariable("type") === "unlock") { + } else if (notificationType === "unlock") { handleTypeUnlock(); - } else if (getQueryVariable("type") === "fileless-import") { + } else if (notificationType === "fileless-import") { handleTypeFilelessImport(); } @@ -146,20 +143,6 @@ function load() { adjustHeight(); } -function getQueryVariable(variable: string) { - const query = window.location.search.substring(1); - const vars = query.split("&"); - - for (let i = 0; i < vars.length; i++) { - const pair = vars[i].split("="); - if (pair[0] === variable) { - return pair[1]; - } - } - - return null; -} - function handleTypeAdd() { setContent(document.getElementById("template-add") as HTMLTemplateElement); @@ -219,7 +202,7 @@ function sendSaveCipherMessage(edit: boolean, folder?: string) { }); } -function handleSaveCipherAttemptCompletedMessage(message: SaveOrUpdateCipherResult) { +function handleSaveCipherAttemptCompletedMessage(message: NotificationBarWindowMessage) { const addSaveButtonContainers = document.querySelectorAll(".add-change-cipher-buttons"); const notificationBarOuterWrapper = document.getElementById("notification-bar-outer-wrapper"); if (message?.error) { @@ -233,7 +216,9 @@ function handleSaveCipherAttemptCompletedMessage(message: SaveOrUpdateCipherResu return; } const messageName = - getQueryVariable("type") === "add" ? "saveCipherAttemptSuccess" : "updateCipherAttemptSuccess"; + notificationBarIframeInitData.type === "add" + ? "saveCipherAttemptSuccess" + : "updateCipherAttemptSuccess"; addSaveButtonContainers.forEach((element) => { element.textContent = chrome.i18n.getMessage(messageName); @@ -261,7 +246,7 @@ function handleTypeUnlock() { * the Bitwarden vault. */ function handleTypeFilelessImport() { - const importType = getQueryVariable("importType"); + const importType = notificationBarIframeInitData.importType; const port = chrome.runtime.connect({ name: FilelessImportPort.NotificationBar }); setContent(document.getElementById("template-fileless-import") as HTMLTemplateElement); @@ -349,7 +334,7 @@ function getSelectedFolder(): string { } function removeIndividualVault(): boolean { - return getQueryVariable("removeIndividualVault") == "true"; + return notificationBarIframeInitData.removeIndividualVault; } function adjustHeight() { @@ -383,3 +368,30 @@ function handleWindowMessage(event: MessageEvent) { handler({ message }); } + +function setupLogoLink(i18n: Record) { + const logoLink = document.getElementById("logo-link") as HTMLAnchorElement; + logoLink.title = i18n.appName; + const setWebVaultUrlLink = (webVaultURL: string) => { + const newVaultURL = webVaultURL && decodeURIComponent(webVaultURL); + if (newVaultURL && newVaultURL !== logoLink.href) { + logoLink.href = newVaultURL; + } + }; + sendPlatformMessage({ command: "getWebVaultUrlForNotification" }, setWebVaultUrlLink); +} + +function setNotificationBarTheme() { + let theme = notificationBarIframeInitData.theme; + if (theme === ThemeType.System) { + theme = window.matchMedia("(prefers-color-scheme: dark)").matches + ? ThemeType.Dark + : ThemeType.Light; + } + + document.documentElement.classList.add(`theme_${theme}`); +} + +function postMessageToParent(message: NotificationBarWindowMessage) { + window.parent.postMessage(message, windowMessageOrigin || "*"); +} diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index b4f87fb31a..9e7fc3f08b 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -453,6 +453,7 @@ export default class MainBackground { this.stateService, this.accountService, this.stateProvider, + this.biometricStateService, ); this.tokenService = new TokenService(this.stateService); this.appIdService = new AppIdService(this.storageService); @@ -491,11 +492,7 @@ export default class MainBackground { this.policyService, ); this.badgeSettingsService = new BadgeSettingsService(this.stateProvider); - this.policyApiService = new PolicyApiService( - this.policyService, - this.apiService, - this.stateService, - ); + this.policyApiService = new PolicyApiService(this.policyService, this.apiService); this.keyConnectorService = new KeyConnectorService( this.stateService, this.cryptoService, @@ -619,6 +616,7 @@ export default class MainBackground { this.tokenService, this.policyService, this.stateService, + this.biometricStateService, ); this.pinCryptoService = new PinCryptoService( @@ -695,7 +693,6 @@ export default class MainBackground { this.folderApiService, this.organizationService, this.sendApiService, - this.stateProvider, logoutCallback, ); this.eventUploadService = new EventUploadService( @@ -833,6 +830,7 @@ export default class MainBackground { this.stateService, this.logService, this.authService, + this.biometricStateService, ); this.commandsBackground = new CommandsBackground( this, @@ -1078,7 +1076,7 @@ export default class MainBackground { await this.eventUploadService.uploadEvents(userId); await Promise.all([ - this.syncService.setLastSync(new Date(0), userId as UserId), + this.syncService.setLastSync(new Date(0), userId), this.cryptoService.clearKeys(userId), this.settingsService.clear(userId), this.cipherService.clear(userId), diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index 3123b32372..e4fb46d960 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -1,3 +1,5 @@ +import { firstValueFrom } from "rxjs"; + import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; @@ -8,6 +10,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; @@ -79,6 +82,7 @@ export class NativeMessagingBackground { private stateService: StateService, private logService: LogService, private authService: AuthService, + private biometricStateService: BiometricStateService, ) { // 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 @@ -321,10 +325,10 @@ export class NativeMessagingBackground { } // Check for initial setup of biometric unlock - const enabled = await this.stateService.getBiometricUnlock(); + const enabled = await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$); if (enabled === null || enabled === false) { if (message.response === "unlocked") { - await this.stateService.setBiometricUnlock(true); + await this.biometricStateService.setBiometricUnlockEnabled(true); } break; } diff --git a/apps/browser/src/background/service-factories/vault-timeout-settings-service.factory.ts b/apps/browser/src/background/service-factories/vault-timeout-settings-service.factory.ts index 9313a27761..febc605bc8 100644 --- a/apps/browser/src/background/service-factories/vault-timeout-settings-service.factory.ts +++ b/apps/browser/src/background/service-factories/vault-timeout-settings-service.factory.ts @@ -9,6 +9,10 @@ import { tokenServiceFactory, TokenServiceInitOptions, } from "../../auth/background/service-factories/token-service.factory"; +import { + biometricStateServiceFactory, + BiometricStateServiceInitOptions, +} from "../../platform/background/service-factories/biometric-state-service.factory"; import { CryptoServiceInitOptions, cryptoServiceFactory, @@ -29,7 +33,8 @@ export type VaultTimeoutSettingsServiceInitOptions = VaultTimeoutSettingsService CryptoServiceInitOptions & TokenServiceInitOptions & PolicyServiceInitOptions & - StateServiceInitOptions; + StateServiceInitOptions & + BiometricStateServiceInitOptions; export function vaultTimeoutSettingsServiceFactory( cache: { vaultTimeoutSettingsService?: AbstractVaultTimeoutSettingsService } & CachedServices, @@ -45,6 +50,7 @@ export function vaultTimeoutSettingsServiceFactory( await tokenServiceFactory(cache, opts), await policyServiceFactory(cache, opts), await stateServiceFactory(cache, opts), + await biometricStateServiceFactory(cache, opts), ), ); } diff --git a/apps/browser/src/platform/background/service-factories/biometric-state-service.factory.ts b/apps/browser/src/platform/background/service-factories/biometric-state-service.factory.ts new file mode 100644 index 0000000000..d2d4d4f983 --- /dev/null +++ b/apps/browser/src/platform/background/service-factories/biometric-state-service.factory.ts @@ -0,0 +1,24 @@ +import { + BiometricStateService, + DefaultBiometricStateService, +} from "@bitwarden/common/platform/biometrics/biometric-state.service"; + +import { FactoryOptions, CachedServices, factory } from "./factory-options"; +import { StateProviderInitOptions, stateProviderFactory } from "./state-provider.factory"; + +type BiometricStateServiceFactoryOptions = FactoryOptions; + +export type BiometricStateServiceInitOptions = BiometricStateServiceFactoryOptions & + StateProviderInitOptions; + +export function biometricStateServiceFactory( + cache: { biometricStateService?: BiometricStateService } & CachedServices, + opts: BiometricStateServiceInitOptions, +): Promise { + return factory( + cache, + "biometricStateService", + opts, + async () => new DefaultBiometricStateService(await stateProviderFactory(cache, opts)), + ); +} diff --git a/apps/browser/src/platform/background/service-factories/crypto-service.factory.ts b/apps/browser/src/platform/background/service-factories/crypto-service.factory.ts index 765530beb6..97614660d1 100644 --- a/apps/browser/src/platform/background/service-factories/crypto-service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/crypto-service.factory.ts @@ -14,6 +14,7 @@ import { } from "../../background/service-factories/log-service.factory"; import { BrowserCryptoService } from "../../services/browser-crypto.service"; +import { biometricStateServiceFactory } from "./biometric-state-service.factory"; import { cryptoFunctionServiceFactory, CryptoFunctionServiceInitOptions, @@ -60,6 +61,7 @@ export function cryptoServiceFactory( await stateServiceFactory(cache, opts), await accountServiceFactory(cache, opts), await stateProviderFactory(cache, opts), + await biometricStateServiceFactory(cache, opts), ), ); } diff --git a/apps/browser/src/platform/services/browser-crypto.service.ts b/apps/browser/src/platform/services/browser-crypto.service.ts index 50cf5a7d75..969dbdf761 100644 --- a/apps/browser/src/platform/services/browser-crypto.service.ts +++ b/apps/browser/src/platform/services/browser-crypto.service.ts @@ -1,15 +1,50 @@ import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; import { CryptoService } from "@bitwarden/common/platform/services/crypto.service"; import { USER_KEY } from "@bitwarden/common/platform/services/key-state/user-key.state"; +import { StateProvider } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; export class BrowserCryptoService extends CryptoService { + constructor( + keyGenerationService: KeyGenerationService, + cryptoFunctionService: CryptoFunctionService, + encryptService: EncryptService, + platformUtilService: PlatformUtilsService, + logService: LogService, + stateService: StateService, + accountService: AccountService, + stateProvider: StateProvider, + private biometricStateService: BiometricStateService, + ) { + super( + keyGenerationService, + cryptoFunctionService, + encryptService, + platformUtilService, + logService, + stateService, + accountService, + stateProvider, + ); + } override async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: UserId): Promise { if (keySuffix === KeySuffixOptions.Biometric) { - return await this.stateService.getBiometricUnlock({ userId: userId }); + const biometricUnlockPromise = + userId == null + ? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$) + : this.biometricStateService.getBiometricUnlockEnabled(userId); + return await biometricUnlockPromise; } return super.hasUserKeyStored(keySuffix, userId); } diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index bcaf8417c9..35849a98e1 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -24,13 +24,8 @@ import { SettingsService } from "@bitwarden/common/abstractions/settings.service import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; -import { - InternalPolicyService, - PolicyService, -} from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service"; import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; @@ -312,17 +307,6 @@ function getBgService(service: keyof MainBackground) { }, deps: [StateServiceAbstraction, StateProvider, OrganizationService], }, - { - provide: PolicyApiServiceAbstraction, - useFactory: ( - policyService: InternalPolicyService, - apiService: ApiService, - stateService: StateService, - ) => { - return new PolicyApiService(policyService, apiService, stateService); - }, - deps: [InternalPolicyService, ApiService, StateService], - }, { provide: PlatformUtilsService, useFactory: getBgService("platformUtilsService"), diff --git a/apps/browser/src/popup/settings/settings.component.ts b/apps/browser/src/popup/settings/settings.component.ts index 1914d2583a..f622cffd3e 100644 --- a/apps/browser/src/popup/settings/settings.component.ts +++ b/apps/browser/src/popup/settings/settings.component.ts @@ -414,7 +414,7 @@ export class SettingsComponent implements OnInit { }), ]); } else { - await this.stateService.setBiometricUnlock(null); + await this.biometricStateService.setBiometricUnlockEnabled(false); await this.stateService.setBiometricFingerprintValidated(false); } } diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 4713b947f0..67c1be16f8 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -38,6 +38,10 @@ import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/s import { ClientType } from "@bitwarden/common/enums"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service"; +import { + BiometricStateService, + DefaultBiometricStateService, +} from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { KeySuffixOptions, LogLevelType } from "@bitwarden/common/platform/enums"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { Account } from "@bitwarden/common/platform/models/domain/account"; @@ -204,6 +208,7 @@ export class Main { derivedStateProvider: DerivedStateProvider; stateProvider: StateProvider; loginStrategyService: LoginStrategyServiceAbstraction; + biometricStateService: BiometricStateService; constructor() { let p = null; @@ -379,11 +384,7 @@ export class Main { this.organizationService, ); - this.policyApiService = new PolicyApiService( - this.policyService, - this.apiService, - this.stateService, - ); + this.policyApiService = new PolicyApiService(this.policyService, this.apiService); this.keyConnectorService = new KeyConnectorService( this.stateService, @@ -490,11 +491,14 @@ export class Main { const lockedCallback = async (userId?: string) => await this.cryptoService.clearStoredUserKey(KeySuffixOptions.Auto); + this.biometricStateService = new DefaultBiometricStateService(this.stateProvider); + this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService( this.cryptoService, this.tokenService, this.policyService, this.stateService, + this.biometricStateService, ); this.pinCryptoService = new PinCryptoService( @@ -547,7 +551,6 @@ export class Main { this.folderApiService, this.organizationService, this.sendApiService, - this.stateProvider, async (expired: boolean) => await this.logout(), ); diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 2b1fb1e883..8708dc8bfc 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "arboard" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index d7659e93f9..0ddd118954 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -14,7 +14,7 @@ manual_test = [] [dependencies] aes = "=0.8.3" -anyhow = "=1.0.75" +anyhow = "=1.0.80" arboard = { version = "=3.3.0", default-features = false, features = ["wayland-data-control"] } base64 = "=0.21.5" cbc = { version = "=0.1.2", features = ["alloc"] } diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 30dfbc57c1..5e71801892 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -24,7 +24,7 @@ "**/node_modules/argon2/package.json", "**/node_modules/argon2/lib/binding/napi-v3/argon2.node" ], - "electronVersion": "28.2.4", + "electronVersion": "28.2.5", "generateUpdatesFilesForAllChannels": true, "publish": { "provider": "generic", diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index ec9d226a45..32aad980f0 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -445,12 +445,12 @@ export class SettingsComponent implements OnInit { try { if (!enabled || !this.supportsBiometric) { this.form.controls.biometric.setValue(false, { emitEvent: false }); - await this.stateService.setBiometricUnlock(null); + await this.biometricStateService.setBiometricUnlockEnabled(false); await this.cryptoService.refreshAdditionalKeys(); return; } - await this.stateService.setBiometricUnlock(true); + await this.biometricStateService.setBiometricUnlockEnabled(true); if (this.isWindows) { // Recommended settings for Windows Hello this.form.controls.requirePasswordOnStart.setValue(true); @@ -465,7 +465,7 @@ export class SettingsComponent implements OnInit { const biometricSet = await this.cryptoService.hasUserKeyStored(KeySuffixOptions.Biometric); this.form.controls.biometric.setValue(biometricSet, { emitEvent: false }); if (!biometricSet) { - await this.stateService.setBiometricUnlock(null); + await this.biometricStateService.setBiometricUnlockEnabled(false); } } finally { this.messagingService.send("redrawMenu"); diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index afc37005df..a42f80fd6f 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -571,7 +571,7 @@ export class AppComponent implements OnInit, OnDestroy { let preLogoutActiveUserId; try { await this.eventUploadService.uploadEvents(userBeingLoggedOut); - await this.syncService.setLastSync(new Date(0), userBeingLoggedOut as UserId); + await this.syncService.setLastSync(new Date(0), userBeingLoggedOut); await this.cryptoService.clearKeys(userBeingLoggedOut); await this.settingsService.clear(userBeingLoggedOut); await this.cipherService.clear(userBeingLoggedOut); diff --git a/apps/desktop/src/auth/lock.component.ts b/apps/desktop/src/auth/lock.component.ts index e88ce17ca5..7403f7481d 100644 --- a/apps/desktop/src/auth/lock.component.ts +++ b/apps/desktop/src/auth/lock.component.ts @@ -172,7 +172,7 @@ export class LockComponent extends BaseLockComponent { return; } - if (await this.stateService.getBiometricUnlock()) { + if (await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$)) { const response = await this.dialogService.openSimpleDialog({ title: { key: "windowsBiometricUpdateWarningTitle" }, content: { key: "windowsBiometricUpdateWarning" }, diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index da4190a1d5..e65cf5fe12 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -561,10 +561,10 @@ "message": "تم إنشاء حسابك الجديد! يمكنك الآن تسجيل الدخول." }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "سجلتَ الدخول بنجاح" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "يمكنك إغلاق هذه النافذة" }, "masterPassSent": { "message": "لقد أرسلنا لك رسالة بريد إلكتروني تحتوي على تلميحات كلمة المرور الرئيسية." @@ -1554,7 +1554,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." }, "currentMasterPass": { @@ -1883,40 +1883,40 @@ "message": "كلمة المرور الرئيسية الخاصة بك لا تفي بواحدة أو أكثر من سياسات مؤسستك. من أجل الوصول إلى الخزنة، يجب عليك تحديث كلمة المرور الرئيسية الآن. سيتم تسجيل خروجك من الجلسة الحالية، مما يتطلب منك تسجيل الدخول مرة أخرى. وقد تظل الجلسات النشطة على أجهزة أخرى نشطة لمدة تصل إلى ساعة واحدة." }, "tryAgain": { - "message": "Try again" + "message": "حاول مرة أخرى" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "التحقق مطلوب لهذا الإجراء. قم بتعيين رمز PIN للمتابعة." }, "setPin": { - "message": "Set PIN" + "message": "تعيين رقم التعريف الشخصي" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "التحقق بواسطة القياسات الحيوية" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "في انتظار تأكيد" }, "couldNotCompleteBiometrics": { - "message": "Could not complete biometrics." + "message": "لا يمكن إكمال القياسات الحيوية." }, "needADifferentMethod": { - "message": "Need a different method?" + "message": "هل تحتاج إلى طريقة مختلفة؟" }, "useMasterPassword": { - "message": "Use master password" + "message": "استخدام كلمة المرور الرئيسية" }, "usePin": { - "message": "Use PIN" + "message": "استخدام PIN" }, "useBiometrics": { - "message": "Use biometrics" + "message": "استخدام المقاييس الحيوية" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "أدخِل رمز التحقق الذي أرسل إلى بريدك الإلكتروني." }, "resendCode": { - "message": "Resend code" + "message": "إعادة إرسال الرمز" }, "hours": { "message": "ساعات" @@ -2532,13 +2532,13 @@ } }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "قم بتشغيل دوو واتبع الخطوات لإنهاء تسجيل الدخول." }, "duoRequiredByOrgForAccount": { - "message": "Duo two-step login is required for your account." + "message": "تسجيل الدخول لـ Duo من خطوتين مطلوب لحسابك." }, "launchDuo": { - "message": "Launch Duo in Browser" + "message": "تشغيل دوو في المتصفح" }, "importFormatError": { "message": "لم يتم تنسيق البيانات بشكل صحيح. الرجاء التحقق من ملف الاستيراد الخاص بك وحاول مرة أخرى." @@ -2621,13 +2621,13 @@ "message": "اسم المستخدم أو كلمة المرور غير صحيحة" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "كلمة سر خاطئة" }, "incorrectCode": { - "message": "Incorrect code" + "message": "رمز غير صحيح" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "رمز PIN غير صحيح" }, "multifactorAuthenticationFailed": { "message": "فشلت المصادقة المتعددة العوامل" diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index e8a47cc6ad..163fcaf43c 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -2535,7 +2535,7 @@ "message": "Inicie o Duo e siga os passos para concluir o início de sessão." }, "duoRequiredByOrgForAccount": { - "message": "A verificação em dois passos Duo é necessária para a sua conta." + "message": "A verificação de dois passos Duo é necessária para a sua conta." }, "launchDuo": { "message": "Iniciar o Duo no navegador" diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 31a1e42a88..0433407590 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -2535,7 +2535,7 @@ "message": "启动 Duo 然后按照步骤完成登录。" }, "duoRequiredByOrgForAccount": { - "message": "您的账户需要 Duo 两步登录。" + "message": "您的账户要求使用 Duo 两步登录。" }, "launchDuo": { "message": "在浏览器中启动 Duo" diff --git a/apps/desktop/src/platform/services/electron-crypto.service.spec.ts b/apps/desktop/src/platform/services/electron-crypto.service.spec.ts index ba86df1d9b..04adfcac70 100644 --- a/apps/desktop/src/platform/services/electron-crypto.service.spec.ts +++ b/apps/desktop/src/platform/services/electron-crypto.service.spec.ts @@ -73,8 +73,8 @@ describe("electronCryptoService", () => { encClientKeyHalf.decrypt = jest.fn().mockResolvedValue(decClientKeyHalf); }); - it("sets an Biometric key if getBiometricUnlock is true and the platform supports secure storage", async () => { - stateService.getBiometricUnlock.mockResolvedValue(true); + it("sets a Biometric key if getBiometricUnlock is true and the platform supports secure storage", async () => { + biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(true); platformUtilService.supportsSecureStorage.mockReturnValue(true); biometricStateService.getRequirePasswordOnStart.mockResolvedValue(true); biometricStateService.getEncryptedClientKeyHalf.mockResolvedValue(encClientKeyHalf); @@ -90,7 +90,7 @@ describe("electronCryptoService", () => { }); it("clears the Biometric key if getBiometricUnlock is false or the platform does not support secure storage", async () => { - stateService.getBiometricUnlock.mockResolvedValue(true); + biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(true); platformUtilService.supportsSecureStorage.mockReturnValue(false); await sut.setUserKey(mockUserKey, mockUserId); diff --git a/apps/desktop/src/platform/services/electron-crypto.service.ts b/apps/desktop/src/platform/services/electron-crypto.service.ts index a7e46dfb1a..6b9327a9c4 100644 --- a/apps/desktop/src/platform/services/electron-crypto.service.ts +++ b/apps/desktop/src/platform/services/electron-crypto.service.ts @@ -1,3 +1,5 @@ +import { firstValueFrom } from "rxjs"; + import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -97,7 +99,11 @@ export class ElectronCryptoService extends CryptoService { protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId?: UserId): Promise { if (keySuffix === KeySuffixOptions.Biometric) { - const biometricUnlock = await this.stateService.getBiometricUnlock({ userId: userId }); + const biometricUnlockPromise = + userId == null + ? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$) + : this.biometricStateService.getBiometricUnlockEnabled(userId); + const biometricUnlock = await biometricUnlockPromise; return biometricUnlock && this.platformUtilService.supportsSecureStorage(); } return await super.shouldStoreKey(keySuffix, userId); diff --git a/apps/desktop/src/services/native-messaging.service.ts b/apps/desktop/src/services/native-messaging.service.ts index e578744188..453b100815 100644 --- a/apps/desktop/src/services/native-messaging.service.ts +++ b/apps/desktop/src/services/native-messaging.service.ts @@ -8,10 +8,12 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { UserId } from "@bitwarden/common/types/guid"; import { DialogService } from "@bitwarden/components"; import { BrowserSyncVerificationDialogComponent } from "../app/components/browser-sync-verification-dialog.component"; @@ -36,6 +38,7 @@ export class NativeMessagingService { private i18nService: I18nService, private messagingService: MessagingService, private stateService: StateService, + private biometricStateService: BiometricStateService, private nativeMessageHandler: NativeMessageHandlerService, private dialogService: DialogService, private ngZone: NgZone, @@ -136,7 +139,11 @@ export class NativeMessagingService { return this.send({ command: "biometricUnlock", response: "not supported" }, appId); } - if (!(await this.stateService.getBiometricUnlock({ userId: message.userId }))) { + const biometricUnlockPromise = + message.userId == null + ? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$) + : this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId); + if (!(await biometricUnlockPromise)) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.send({ command: "biometricUnlock", response: "not enabled" }, appId); diff --git a/apps/web/src/app/core/state/state.service.ts b/apps/web/src/app/core/state/state.service.ts index b46c2b590a..1ad3bd25c3 100644 --- a/apps/web/src/app/core/state/state.service.ts +++ b/apps/web/src/app/core/state/state.service.ts @@ -80,4 +80,14 @@ export class StateService extends BaseStateService { options = this.reconcileOptions(options, await this.defaultInMemoryOptions()); return await super.setEncryptedSends(value, options); } + + override async getLastSync(options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultInMemoryOptions()); + return await super.getLastSync(options); + } + + override async setLastSync(value: string, options?: StorageOptions): Promise { + options = this.reconcileOptions(options, await this.defaultInMemoryOptions()); + return await super.setLastSync(value, options); + } } diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 8b740f4be8..6267ce372c 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,11 +6673,11 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Lid/Groep" + "memberColumnHeader": { + "message": "Member" }, - "groupAndMemberColumnHeader": { - "message": "Lid" + "groupSlashMemberColumnHeader": { + "message": "Group/Member" }, "selectGroupsAndMembers": { "message": "Kies groepe en lede" @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 19f27ab665..b1fdbb6059 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 2e5b777e1a..5c76275188 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Daxilə köçürüləcək datanız yoxdursa, əvəzində yeni element yarada bilərsiniz.", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": " ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": "Administratorunuz təşkilat üzvlüyünüzü təsdiqləyənə qədər gözləməli ola bilərsiniz.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Kodu təkrar göndər" }, - "membersColumnHeader": { - "message": "Üzv/Qrup" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Üzv" }, + "groupSlashMemberColumnHeader": { + "message": "Qrup/Üzv" + }, "selectGroupsAndMembers": { "message": "Qrup və üzv seç" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Qrup və ya üzvlərin bu kolleksiyaya müraciətinə icazə verin." }, + "grantCollectionAccessMembersOnly": { + "message": "Üzvlərin bu kolleksiyaya müraciətinə icazə verin." + }, "adminCollectionAccess": { "message": "Administratorlar kolleksiyalara müraciət edə və onları idarə edə bilər." }, @@ -7591,9 +7598,9 @@ "message": "Buraxılış bloqunu oxuyun" }, "adminConsole": { - "message": "Admin Console" + "message": "Admin Konsolu" }, "providerPortal": { - "message": "Provider Portal" + "message": "Provayder Portal" } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 618d91bc68..2740b1b37b 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,11 +6673,11 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Удзельнік/Група" + "memberColumnHeader": { + "message": "Member" }, - "groupAndMemberColumnHeader": { - "message": "Удзельнік" + "groupSlashMemberColumnHeader": { + "message": "Group/Member" }, "selectGroupsAndMembers": { "message": "Выбраць групы і ўдзельнікаў" @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 4726ad8ae5..7f5ba71d68 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -1349,14 +1349,18 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Ако нямате данни за внасяне, вместо това може да създадете ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "нов елемент", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { - "message": ". Може да трябва да почакате, докато администраторът потвърди членството Ви в организацията.", + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": ".", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { + "message": ". Може да трябва да почакате администратора на Вашата организация да потвърди членството Ви.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Повторно изпращане на кода" }, - "membersColumnHeader": { - "message": "Член/група" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Член" }, + "groupSlashMemberColumnHeader": { + "message": "Група/член" + }, "selectGroupsAndMembers": { "message": "Изберете групи и членове" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Дайте права на групи и членове до тази колекция." }, + "grantCollectionAccessMembersOnly": { + "message": "Дайте права на членовете до тази колекция." + }, "adminCollectionAccess": { "message": "Администраторите имат достъп до и могат да управляват колекциите." }, diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 43a218e3d2..86f8928d25 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 6f743a1395..3a32618918 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 8bdfc49023..cf1f371323 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -1349,14 +1349,18 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Si no teniu cap dada per importar, podeu crear un ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "element nou.", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { - "message": " És possible que hàgeu d'esperar fins que l'administrador confirme la vostra pertinença a l'organització.", + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " en compte d'això.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { + "message": " en compte d'això. És possible que hàgeu d'esperar fins que l'administrador confirme la vostra pertinença a l'organització.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Reenvia el codi" }, - "membersColumnHeader": { - "message": "Membre/grup" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Membre" }, + "groupSlashMemberColumnHeader": { + "message": "Grup/Membre" + }, "selectGroupsAndMembers": { "message": "Seleccioneu grups i membres" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Concedeix als grups o membres l'accés a aquesta col·lecció." }, + "grantCollectionAccessMembersOnly": { + "message": "Concedeix als membres l'accés a aquesta col·lecció." + }, "adminCollectionAccess": { "message": "Els administradors poden accedir i gestionar les col·leccions." }, diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 857cb38597..4504d840e8 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -1349,14 +1349,18 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Pokud nemáte žádná data k importu, můžete místo toho vytvořit ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { - "message": "novou položku.", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "message": "novou položku", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { - "message": " Možná budete muset počkat, než Váš správce potvrdí členství ve Vaší organizaci.", + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": ".", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { + "message": ". Možná budete muset počkat, než Váš správce potvrdí členství ve Vaší organizaci.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Znovu odeslat kód" }, - "membersColumnHeader": { - "message": "Člen/Skupina" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Člen" }, + "groupSlashMemberColumnHeader": { + "message": "Skupina/Člen" + }, "selectGroupsAndMembers": { "message": "Zvolte skupiny a členy" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Udělí skupinám nebo členům přístup k této kolekci." }, + "grantCollectionAccessMembersOnly": { + "message": "Udělí členům přístup k této kolekci." + }, "adminCollectionAccess": { "message": "Správci mohou přistupovat ke kolekcím a spravovat je." }, diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 5218a0d9c9..2a7aa7f7be 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index c6202d615d..c650c9f269 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Har man igen data at importere, kan man oprette et ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "nyt emne", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " i stedet.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " i stedet. Man skal muligvis afvente, at administratoren bekræfter organisationsmedlemskabet.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Send kode igen" }, - "membersColumnHeader": { - "message": "Medlem/Gruppe" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Medlem" }, + "groupSlashMemberColumnHeader": { + "message": "Gruppe/Medlem" + }, "selectGroupsAndMembers": { "message": "Vælg grupper og medlemmer" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Tildel grupper eller medlemmer adgang til denne samling." }, + "grantCollectionAccessMembersOnly": { + "message": "Tildel medlemmer adgang til denne samling." + }, "adminCollectionAccess": { "message": "Administratorer kan tilgå og håndtere samlinger." }, diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 77d94218df..82b552879d 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Wenn du keine Daten zu importieren hast, kannst du stattdessen einen ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "neuen Eintrag", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " erstellen.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " erstellen. Möglicherweise musst du warten, bis dein Administrator die Mitgliedschaft in deiner Organisation bestätigt.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Code erneut senden" }, - "membersColumnHeader": { - "message": "Mitglied/Gruppe" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Mitglied" }, + "groupSlashMemberColumnHeader": { + "message": "Gruppe/Mitglied" + }, "selectGroupsAndMembers": { "message": "Gruppen und Mitglieder auswählen" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Gewähre Gruppen oder Mitgliedern Zugriff auf diese Sammlung." }, + "grantCollectionAccessMembersOnly": { + "message": "Gewähre Mitgliedern Zugriff auf diese Sammlung." + }, "adminCollectionAccess": { "message": "Administratoren können auf Sammlungen zugreifen und diese verwalten." }, @@ -7582,13 +7589,13 @@ "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "freeForOneYear": { - "message": "Free for 1 year" + "message": "Kostenlos für 1 Jahr" }, "newWebApp": { - "message": "Welcome to the new and improved web app. Learn more about what’s changed." + "message": "Willkommen bei der neuen und verbesserten Web-App. Erfahre mehr über die Änderungen." }, "releaseBlog": { - "message": "Read release blog" + "message": "Veröffentlichungs-Blog lesen" }, "adminConsole": { "message": "Administrator-Konsole" diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index f72b22f024..050c4bf3b8 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index d87dfe4f57..1b0498c29f 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index e91ede1b8b..508731f596 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 45381c4173..4fab207997 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 5188d31b1e..df5fa79898 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -1349,14 +1349,18 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Si no tiene ningún dato para importar, puede crear un ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "nuevo elemento", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { - "message": " en su lugar. Puede que tenga que esperar hasta que su administrador confirme la pertenencia a su organización.", + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { + "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -6669,11 +6673,11 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Miembro/Grupo" + "memberColumnHeader": { + "message": "Member" }, - "groupAndMemberColumnHeader": { - "message": "Miembro" + "groupSlashMemberColumnHeader": { + "message": "Group/Member" }, "selectGroupsAndMembers": { "message": "Seleccionar grupos y miembros" @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index af7fde978f..c5e9643ce7 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 99b86bcf80..7328de0432 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 6ffeac8e9b..381118a4c7 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,11 +6673,11 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "عضو/گروه" + "memberColumnHeader": { + "message": "Member" }, - "groupAndMemberColumnHeader": { - "message": "عضو" + "groupSlashMemberColumnHeader": { + "message": "Group/Member" }, "selectGroupsAndMembers": { "message": "گروه‌ها و اعضا را انتخاب کنید" @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 54ff443fb2..c6a62ea04b 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Jos sinulla ei ole tuotavia tietoja, voit tuonnin sijaan luoda ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "uuden kohteen", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": ".", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": ". Saatat joutua odottamaan, että organisaation ylläpito vahvistaa jäsenyytesi organisaatiossa.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -4944,7 +4948,7 @@ "message": "Luo uusi asiakasorganisaatio, joka liitetään sinuun toimittajana. Voit käyttää ja hallita tätä organisaatiota." }, "newClient": { - "message": "Uusi asiakas" + "message": "Uusi pääte" }, "addExistingOrganization": { "message": "Lisää olemassa oleva organisaatio" @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Lähetä koodi uudelleen" }, - "membersColumnHeader": { - "message": "Jäsen/ryhmä" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Jäsen" }, + "groupSlashMemberColumnHeader": { + "message": "Ryhmä/Jäsen" + }, "selectGroupsAndMembers": { "message": "Valitse ryhmät ja jäsenet" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Myönnä ryhmille tai henkilöille kokoelman käyttöoikeus." }, + "grantCollectionAccessMembersOnly": { + "message": "Myönnä jäsenille tämän kokoelman käyttöoikeus." + }, "adminCollectionAccess": { "message": "Ylläpitäjät voivat käyttää ja hallinnoida kokoelmia." }, diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 6f12fa08f0..d1eff1f523 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,11 +6673,11 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Miyembro/Grupo" + "memberColumnHeader": { + "message": "Member" }, - "groupAndMemberColumnHeader": { - "message": "Miyembro" + "groupSlashMemberColumnHeader": { + "message": "Group/Member" }, "selectGroupsAndMembers": { "message": "Pumili ng mga grupo at miyembro" @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 56687cfecd..29d709dd9d 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Si vous n'avez aucune donnée à importer, vous pouvez créer un ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "nouvel élément", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " à la place.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " à la place. Vous devrez peut-être attendre que votre administrateur confirme votre adhésion à l'organisation.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Renvoyer le code" }, - "membersColumnHeader": { - "message": "Membre/Groupe" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Membre" }, + "groupSlashMemberColumnHeader": { + "message": "Groupe/Membre" + }, "selectGroupsAndMembers": { "message": "Sélectionner les groupes et les membres" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Accorder l'accès à cette collection aux groupes ou aux membres." }, + "grantCollectionAccessMembersOnly": { + "message": "Accorder l'accès à cette collection aux membres." + }, "adminCollectionAccess": { "message": "Les administrateurs peuvent accéder et gérer les collections." }, diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index bbc92817c1..bfac953fd1 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index c4e3a22f66..419c99ae04 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 3decaf856b..8375f486b7 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 4e97cea27d..6b0ed0e4ed 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,11 +6673,11 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Član/grupa" + "memberColumnHeader": { + "message": "Member" }, - "groupAndMemberColumnHeader": { - "message": "Član" + "groupSlashMemberColumnHeader": { + "message": "Group/Member" }, "selectGroupsAndMembers": { "message": "Odaberi grupe i članove" @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 3bf0c6c632..1491e70457 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -1349,14 +1349,18 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Ha nincsenek importálandó adatok, létrehozhatunk egy", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "új elemet", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { - "message": "helyette. Előfordulhat, hogy meg kell várni, amíg az adminisztrátor megerősíti szervezeti tagságot.", + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " helyett.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { + "message": " helyett. Előfordulhat, hogy meg kell várni, amíg az adminisztrátor megerősíti szervezeti tagságot.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Kód újraküldése" }, - "membersColumnHeader": { - "message": "Tag/Csoport" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Tag" }, + "groupSlashMemberColumnHeader": { + "message": "Csoport/Tag" + }, "selectGroupsAndMembers": { "message": "Csoportok és tagok kiválasztása" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Adjunk hozzáférést csoportoknak vagy személyeknek eennél a gyűjteménynél." }, + "grantCollectionAccessMembersOnly": { + "message": "A tagok hozzáférésének megadása ehhez a gyűjteményhez." + }, "adminCollectionAccess": { "message": "Az adminisztrátorok elérhetik és kezelhetik a gyűjteményeket." }, diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index a3c889ccef..5687016656 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index ecbeffd6cf..e8a1d2a0ee 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Se non hai dati da importare, puoi invece creare un ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "nuovo elemento", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": ".", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": ". Potresti dover attendere finché l'amministratore non conferma l'appartenenza all'organizzazione.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Invia codice di nuovo" }, - "membersColumnHeader": { - "message": "Membro/gruppo" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Membro" }, + "groupSlashMemberColumnHeader": { + "message": "Gruppo/Membro" + }, "selectGroupsAndMembers": { "message": "Seleziona gruppi e membri" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Consenti a gruppi o membri di accedere a questa raccolta." }, + "grantCollectionAccessMembersOnly": { + "message": "Concedi ai membri l'accesso a questa raccolta." + }, "adminCollectionAccess": { "message": "Gli amministratori possono accedere e gestire le raccolte." }, diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 8b4ddf6d89..641e041c22 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -1349,14 +1349,18 @@ }, "onboardingImportDataDetailsPartOne": { "message": "インポートするデータがない場合は、 ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "新しいアイテム", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { - "message": "を作成できます。管理者が組織のメンバーシップを確認するまで待つ必要があります。", + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": "を作成してください。", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { + "message": "を作成してください。管理者が組織のメンバーシップを確認するまで待つ必要があります。", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -6669,12 +6673,12 @@ "resendCode": { "message": "コードを再送信する" }, - "membersColumnHeader": { - "message": "メンバー/グループ" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "メンバー" }, + "groupSlashMemberColumnHeader": { + "message": "グループ/メンバー" + }, "selectGroupsAndMembers": { "message": "グループとメンバーを選択" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "グループまたはメンバーにこのコレクションへのアクセスを許可します。" }, + "grantCollectionAccessMembersOnly": { + "message": "メンバーにこのコレクションへのアクセスを許可します。" + }, "adminCollectionAccess": { "message": "管理者はコレクションにアクセスし、管理することができます。" }, diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 8fa4d6afed..382ce26c4c 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index bbc92817c1..bfac953fd1 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index a3fecb7b09..875cb47da8 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 0dbdb73bff..e8b55e2296 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,11 +6673,11 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" + "memberColumnHeader": { + "message": "Member" }, - "groupAndMemberColumnHeader": { - "message": "구성원" + "groupSlashMemberColumnHeader": { + "message": "Group/Member" }, "selectGroupsAndMembers": { "message": "Select groups and members" @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index cefd974c1c..d8cc1c879b 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Ja nav nekādu ievietojamu datu, var izveidot ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "jaunu vienumu", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": ".", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": ". Var būt nepieciešams gaidīt, līdz pārvaldnieks apstiprina dalību apvienībā.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Atkārtoti nosūtīt kodu" }, - "membersColumnHeader": { - "message": "Dalībnieks/Grupa" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Dalībnieks" }, + "groupSlashMemberColumnHeader": { + "message": "Kopa/dalībnieks" + }, "selectGroupsAndMembers": { "message": "Atlasīt grupas un dalībniekus" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Piešķirt kopām vai dalībniekiem piekļuvi šim krājumam." }, + "grantCollectionAccessMembersOnly": { + "message": "Piešķirt dalībniekiem piekļuvi šim krājumam." + }, "adminCollectionAccess": { "message": "Pārvaldnieki var piekļūt krājumiem un pārvaldīt tos." }, diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index d72ff71e08..85aa4d1857 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index bbc92817c1..bfac953fd1 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index bbc92817c1..bfac953fd1 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index b7ca6de869..1a92e78aa5 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,11 +6673,11 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Medlem/gruppe" + "memberColumnHeader": { + "message": "Member" }, - "groupAndMemberColumnHeader": { - "message": "Medlem" + "groupSlashMemberColumnHeader": { + "message": "Group/Member" }, "selectGroupsAndMembers": { "message": "Velg grupper og medlemmer" @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index ca983effaa..34dbe80947 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index e7895b1c57..35a81b2dde 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -1349,14 +1349,18 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Als je geen gegevens om te importeren hebt, kun je een ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "nieuw item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { - "message": " maken. Je moet misschien wachten tot je beheerder je organisatielidmaatschap bevestigt.", + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " aanmaken.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { + "message": " aanmaken. Je moet misschien wachten tot je beheerder je organisatielidmaatschap bevestigt.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Code opnieuw sturen" }, - "membersColumnHeader": { - "message": "Lid/Groep" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Lid" }, + "groupSlashMemberColumnHeader": { + "message": "Groep/Lid" + }, "selectGroupsAndMembers": { "message": "Groepen en leden selecteren" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Groepen of mensen toegang tot deze collectie geven." }, + "grantCollectionAccessMembersOnly": { + "message": "Leden toegang tot deze collectie geven." + }, "adminCollectionAccess": { "message": "Beheerders hebben toegang tot collecties en kunnen deze beheren." }, diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index ed2d6b19bd..54c3ccce85 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index bbc92817c1..bfac953fd1 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 47b89ab8cc..0268f07449 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,11 +6673,11 @@ "resendCode": { "message": "Wysłać kod ponownie" }, - "membersColumnHeader": { - "message": "Użytkownik/Grupa" + "memberColumnHeader": { + "message": "Member" }, - "groupAndMemberColumnHeader": { - "message": "Użytkownik" + "groupSlashMemberColumnHeader": { + "message": "Group/Member" }, "selectGroupsAndMembers": { "message": "Wybierz grupy i użytkowników" @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Przyznaj grupom lub członkom dostęp do tej kolekcji." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administratorzy mogą mieć dostęp do kolekcji i zarządzać nimi." }, diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index f42e3a8d13..a2b4523fc3 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -1349,14 +1349,18 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Se você não tem dados para importar, você pode criar uma ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "Novo Item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { - "message": " em vez disso, você pode precisar esperar até que o seu administrador confirme a sua associação à organização.", + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { + "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -6669,11 +6673,11 @@ "resendCode": { "message": "Reenviar código" }, - "membersColumnHeader": { - "message": "Membro/Grupo" + "memberColumnHeader": { + "message": "Member" }, - "groupAndMemberColumnHeader": { - "message": "Membro" + "groupSlashMemberColumnHeader": { + "message": "Group/Member" }, "selectGroupsAndMembers": { "message": "Selecione grupos e membros" @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 7f7fb0f046..8b40e261d5 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -1349,14 +1349,18 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Se não tiver quaisquer dados para importar, pode criar um ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "novo item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { - "message": " Poderá ser necessário aguardar até que o administrador confirme a filiação da sua organização.", + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": ".", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { + "message": " . Poderá ser necessário aguardar até que o administrador confirme a filiação da sua organização.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -5933,7 +5937,7 @@ "message": "Inicie o DUO e siga os passos para concluir o início de sessão." }, "duoRequiredByOrgForAccount": { - "message": "A verificação em dois passos DUO é necessária para a sua conta." + "message": "A verificação de dois passos Duo é necessária para a sua conta." }, "launchDuo": { "message": "Iniciar o DUO" @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Reenviar código" }, - "membersColumnHeader": { - "message": "Membro/Grupo" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Membro" }, + "groupSlashMemberColumnHeader": { + "message": "Grupo/Membro" + }, "selectGroupsAndMembers": { "message": "Selecionar grupos e membros" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Conceder a grupos ou membros acesso a esta coleção." }, + "grantCollectionAccessMembersOnly": { + "message": "Conceder a membros acesso a esta coleção." + }, "adminCollectionAccess": { "message": "Os administradores podem aceder e gerir coleções." }, diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 31466b2d35..1c9ca031cc 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 1cdf298b02..65262e6f16 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -1349,14 +1349,18 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Если у вас нет данных для импорта, вы можете создать ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "новый элемент.", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { - "message": " Возможно, вам придется подождать, пока администратор подтвердит ваше членство в организации.", + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " вместо этого.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { + "message": " вместо этого. Возможно, вам придется подождать, пока администратор подтвердит ваше членство в организации.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Отправить код повторно" }, - "membersColumnHeader": { - "message": "Участник/Группа" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Участник" }, + "groupSlashMemberColumnHeader": { + "message": "Группа/участник" + }, "selectGroupsAndMembers": { "message": "Выбор групп и участников" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Предоставить группам или участникам доступ к этой коллекции." }, + "grantCollectionAccessMembersOnly": { + "message": "Предоставить участникам доступ к этой коллекции." + }, "adminCollectionAccess": { "message": "Администраторы могут получать доступ к коллекциям и управлять ими." }, diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 7ba451770e..6febaab3f1 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index cd49a49860..0e6c5de678 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Ak nemáte žiadne dáta na import, môžete namiesto toho vytvoriť ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "novú položku", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": "namiesto toho.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": ". Možno budete musieť počkať kým správca potvrdí vaše členstvo v organizácii.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Znova odoslať kód" }, - "membersColumnHeader": { - "message": "Člen/Skupina" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Člen" }, + "groupSlashMemberColumnHeader": { + "message": "Skupina/Člen" + }, "selectGroupsAndMembers": { "message": "Vyberte skupiny a členov" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Povoľte skupinám, alebo jednotlivcom prístup k tejto zbierke." }, + "grantCollectionAccessMembersOnly": { + "message": "Povoľte členom prístup k tejto zbierke." + }, "adminCollectionAccess": { "message": "Správcovia môžu pristupovať k a spravovať zbierky." }, diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 4cf2893f25..e67b8a3c45 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 65cdb979aa..34ad171c4f 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -1349,14 +1349,18 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Ако немате податке за увоз, можете креирати ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "нову ставку", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { - "message": " Можда ћете морати да сачекате док администратор не потврди чланство.", + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " уместо.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { + "message": " уместо. Можда ћете морати да сачекате док администратор не потврди чланство.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -4944,7 +4948,7 @@ "message": "Креирајте нову организацију клијента која ће бити повезана са вама као провајдера. Бићете у могућности да приступите и управљате овој организацијом." }, "newClient": { - "message": "New client" + "message": "Нови клијент" }, "addExistingOrganization": { "message": "Додај постојећу организацију" @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Поново послати кôд" }, - "membersColumnHeader": { - "message": "Члан/Група" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Члан" }, + "groupSlashMemberColumnHeader": { + "message": "Група/Члан" + }, "selectGroupsAndMembers": { "message": "Изаберите групе и чланове" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Одобрите групама или члановима приступ овој колекцији." }, + "grantCollectionAccessMembersOnly": { + "message": "Одобрите члановима приступ овој колекцији." + }, "adminCollectionAccess": { "message": "Администратори могу да приступе и управљају колекцијама." }, @@ -7582,18 +7589,18 @@ "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "freeForOneYear": { - "message": "Free for 1 year" + "message": "Бесплатно 1 годину" }, "newWebApp": { - "message": "Welcome to the new and improved web app. Learn more about what’s changed." + "message": "Добродошли у нову и побољшану веб апликацију. Сазнајте више о томе шта се променило." }, "releaseBlog": { - "message": "Read release blog" + "message": "Прочитајте блог о издању" }, "adminConsole": { - "message": "Admin Console" + "message": "Администраторска конзола" }, "providerPortal": { - "message": "Provider Portal" + "message": "Портал провајдера" } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 477206bf5c..c319196f8f 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 7e5e98e998..24b6b32143 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Om du inte har någon data att importera, kan du skapa ett ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "nytt objekt", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Skicka kod igen" }, - "membersColumnHeader": { - "message": "Medlem/Grupp" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Medlem" }, + "groupSlashMemberColumnHeader": { + "message": "Grupp/Medlem" + }, "selectGroupsAndMembers": { "message": "Välj grupper och medlemmar" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Ge grupper eller medlemmar tillgång till denna samling." }, + "grantCollectionAccessMembersOnly": { + "message": "Ge medlemmar tillgång till denna samling." + }, "adminCollectionAccess": { "message": "Administratörer kan komma åt och hantera samlingar." }, diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index bbc92817c1..bfac953fd1 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index e4488e711f..4848e8f335 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 6bf5c94ee9..f94dd470aa 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -1349,14 +1349,18 @@ }, "onboardingImportDataDetailsPartOne": { "message": "İçe aktarılacak veriniz yoksa bir tane oluşturun ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "yeni öge", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { - "message": " onun yerine. Yöneticiniz kuruluş üyeliğinizi onaylayana kadar beklemeniz gerekebilir.", + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " yerine.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { + "message": " yerine. Yöneticiniz kuruluş üyeliğinizi onaylayana kadar beklemeniz gerekebilir.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Kodu yeniden gönder" }, - "membersColumnHeader": { - "message": "Üye/Grup" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Üye" }, + "groupSlashMemberColumnHeader": { + "message": "Grup/Üye" + }, "selectGroupsAndMembers": { "message": "Grupları ve üyeleri seçin" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Gruplara veya üyelere bu koleksiyona erişim izni verin." }, + "grantCollectionAccessMembersOnly": { + "message": "Üyelere bu koleksiyona erişim izni verin." + }, "adminCollectionAccess": { "message": "Yöneticiler koleksiyonlara erişebilir ve bunları yönetebilir." }, diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index c46c011868..f7c2086323 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "Якщо у вас немає даних для імпорту, можете створити ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "новий елемент", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " натомість.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " натомість. Можливо, доведеться зачекати доки адміністратор підтвердить вашу участь в організації.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Надіслати новий код" }, - "membersColumnHeader": { - "message": "Учасник/Група" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Учасник" }, + "groupSlashMemberColumnHeader": { + "message": "Група/учасник" + }, "selectGroupsAndMembers": { "message": "Виберіть групи та учасників" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Надайте групам або учасникам доступ до цієї збірки." }, + "grantCollectionAccessMembersOnly": { + "message": "Надайте учасникам доступ до цієї збірки." + }, "adminCollectionAccess": { "message": "Адміністратори мають доступ і можливість керування збірками." }, diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 96d2c99be2..e5f61d467b 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,12 +6673,12 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "Member/Group" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "Member" }, + "groupSlashMemberColumnHeader": { + "message": "Group/Member" + }, "selectGroupsAndMembers": { "message": "Select groups and members" }, @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 85fbe9e5cf..62f3e00cde 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "如果您没有任何数据要导入,您可以创建一个 ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "新项目", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " 。", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " 。您可能需要等待您的管理员确认您的组织成员资格。", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -2534,7 +2538,7 @@ } }, "trialSecretsManagerThankYou": { - "message": "Thanks for signing up for Bitwarden Secrets Manager for $PLAN$!", + "message": "感谢您注册适用于 $PLAN$ 的 Bitwarden 机密管理器!", "placeholders": { "plan": { "content": "$1", @@ -5203,7 +5207,7 @@ "message": "设置一个唯一的 SP 实体 ID" }, "spUniqueEntityIdDesc": { - "message": "Generate an identifier that is unique to your organization" + "message": "生成您的组织独有的标识符" }, "idpEntityId": { "message": "实体 ID" @@ -5930,13 +5934,13 @@ } }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "启动 Duo 然后按照步骤完成登录。" }, "duoRequiredByOrgForAccount": { - "message": "Duo two-step login is required for your account." + "message": "您的账户要求使用 Duo 两步登录。" }, "launchDuo": { - "message": "Launch Duo" + "message": "启动 Duo" }, "turnOn": { "message": "开启" @@ -6669,12 +6673,12 @@ "resendCode": { "message": "重新发送代码" }, - "membersColumnHeader": { - "message": "成员/群组" - }, - "groupAndMemberColumnHeader": { + "memberColumnHeader": { "message": "成员" }, + "groupSlashMemberColumnHeader": { + "message": "群组/成员" + }, "selectGroupsAndMembers": { "message": "选择群组和成员" }, @@ -7249,7 +7253,7 @@ "message": "请求了设备批准。" }, "startYour7DayFreeTrialOfBitwardenFor": { - "message": "为 $ORG$ 开始您的 Bitwarden 7 天免费试用", + "message": "开始 $ORG$ 的 Bitwarden 7 天免费试用", "placeholders": { "org": { "content": "$1", @@ -7258,7 +7262,7 @@ } }, "startYour7DayFreeTrialOfBitwardenSecretsManagerFor": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for $ORG$", + "message": "开始 $ORG$ 的 Bitwarden 机密管理器 7 天免费试用", "placeholders": { "org": { "content": "$1", @@ -7477,7 +7481,7 @@ "message": "安装浏览器扩展" }, "installBrowserExtensionDetails": { - "message": "使用扩展快速保存登录信息和自动填充表单,无需打开网页应用程序。" + "message": "使用扩展快速保存登录信息和自动填充表单,而无需打开网页应用程序。" }, "projectAccessUpdated": { "message": "工程访问权限已更新" @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "授予群组或成员对此集合的访问权限。" }, + "grantCollectionAccessMembersOnly": { + "message": "授予成员对此集合的访问权限。" + }, "adminCollectionAccess": { "message": "管理员可以访问和管理集合。" }, @@ -7525,32 +7532,32 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'" }, "collectionEnhancementsDesc": { - "message": "为额外的灵活性添加新的设置和权限。 将管理员角色替换为“可以管理”权限。 并引入选项以允许用户创建集合,并限制对集合的管理访问。", + "message": "添加新的设置和权限以获得额外的灵活性。将经理角色替换为「可以管理」权限,并引入允许用户创建集合以及限制对集合的管理访问权限的选项。", "description": "This describes new features and improvements for user roles and collections" }, "collectionEnhancementsLearnMore": { - "message": "了解集合管理" + "message": "了解更多关于集合管理" }, "organizationInformation": { - "message": "Organization information" + "message": "组织信息" }, "confirmationDetails": { - "message": "Confirmation details" + "message": "确认详细信息" }, "smFreeTrialThankYou": { - "message": "Thank you for signing up for Bitwarden Secrets Manager!" + "message": "感谢您注册 Bitwarden 机密管理器!" }, "smFreeTrialConfirmationEmail": { - "message": "We've sent a confirmation email to your email at " + "message": "我们已经发送了一封确认邮件到 " }, "confirmCollectionEnhancementsDialogTitle": { - "message": "This action is irreversible" + "message": "此操作不可逆" }, "confirmCollectionEnhancementsDialogContent": { - "message": "Turning on this feature will deprecate the manager role and replace it with a Can manage permission. This will take a few moments. Do not make any organization changes until it is complete. Are you sure you want to proceed?" + "message": "开启此功能将弃用经理角色,并将其替换为「可以管理」权限。这需要一些时间。在完成之前,请勿对组织进行任何更改。您确定要继续吗?" }, "sorryToSeeYouGo": { - "message": "很遗憾看到您离开!通过分享您取消的原因,帮助改进 Bitwarden。", + "message": "很遗憾看到您离开!请分享您取消的原因,以帮助改进 Bitwarden。", "description": "A message shown to users as part of an offboarding survey asking them to provide more information on their subscription cancelation." }, "selectCancellationReason": { @@ -7558,7 +7565,7 @@ "description": "Used as a form field label for a select input on the offboarding survey." }, "anyOtherFeedback": { - "message": "是否有其他您想要分享的反馈?", + "message": "您还有其他反馈要分享吗?", "description": "Used as a form field label for a textarea input on the offboarding survey." }, "missingFeatures": { @@ -7566,7 +7573,7 @@ "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "movingToAnotherTool": { - "message": "迁移到另一个工具", + "message": "迁移到其他工具", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "tooDifficultToUse": { @@ -7594,6 +7601,6 @@ "message": "管理控制台" }, "providerPortal": { - "message": "Provider Portal" + "message": "提供商门户" } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 635eaab2a4..4160787175 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -1349,13 +1349,17 @@ }, "onboardingImportDataDetailsPartOne": { "message": "If you don't have any data to import, you can create a ", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { "message": "new item", - "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, - "onboardingImportDataDetailsPartTwo": { + "onboardingImportDataDetailsPartTwoNoOrgs": { + "message": " instead.", + "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." + }, + "onboardingImportDataDetailsPartTwoWithOrgs": { "message": " instead. You may need to wait until your administrator confirms your organization membership.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, @@ -6669,11 +6673,11 @@ "resendCode": { "message": "Resend code" }, - "membersColumnHeader": { - "message": "成員/群組" + "memberColumnHeader": { + "message": "Member" }, - "groupAndMemberColumnHeader": { - "message": "成員" + "groupSlashMemberColumnHeader": { + "message": "Group/Member" }, "selectGroupsAndMembers": { "message": "選擇群組和成員" @@ -7500,6 +7504,9 @@ "grantCollectionAccess": { "message": "Grant groups or members access to this collection." }, + "grantCollectionAccessMembersOnly": { + "message": "Grant members access to this collection." + }, "adminCollectionAccess": { "message": "Administrators can access and manage collections." }, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 6f6f3745b6..3803e95911 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -139,10 +139,10 @@ import { ValidationService } from "@bitwarden/common/platform/services/validatio import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service"; import { ActiveUserStateProvider, - DerivedStateProvider, GlobalStateProvider, SingleUserStateProvider, StateProvider, + DerivedStateProvider, } from "@bitwarden/common/platform/state"; /* eslint-disable import/no-restricted-paths -- We need the implementations to inject, but generally these should not be accessed */ import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider"; @@ -517,7 +517,6 @@ import { ModalService } from "./modal.service"; FolderApiServiceAbstraction, OrganizationServiceAbstraction, SendApiServiceAbstraction, - StateProvider, LOGOUT_CALLBACK, ], }, @@ -535,6 +534,7 @@ import { ModalService } from "./modal.service"; TokenServiceAbstraction, PolicyServiceAbstraction, StateServiceAbstraction, + BiometricStateService, ], }, { @@ -683,7 +683,7 @@ import { ModalService } from "./modal.service"; { provide: PolicyApiServiceAbstraction, useClass: PolicyApiService, - deps: [PolicyServiceAbstraction, ApiServiceAbstraction, StateServiceAbstraction], + deps: [InternalPolicyService, ApiServiceAbstraction], }, { provide: KeyConnectorServiceAbstraction, diff --git a/libs/common/src/admin-console/services/policy/policy-api.service.ts b/libs/common/src/admin-console/services/policy/policy-api.service.ts index 4ca4063f6c..0eb37f2305 100644 --- a/libs/common/src/admin-console/services/policy/policy-api.service.ts +++ b/libs/common/src/admin-console/services/policy/policy-api.service.ts @@ -4,7 +4,6 @@ import { ApiService } from "../../../abstractions/api.service"; import { HttpStatusCode } from "../../../enums"; import { ErrorResponse } from "../../../models/response/error.response"; import { ListResponse } from "../../../models/response/list.response"; -import { StateService } from "../../../platform/abstractions/state.service"; import { Utils } from "../../../platform/misc/utils"; import { PolicyApiServiceAbstraction } from "../../abstractions/policy/policy-api.service.abstraction"; import { InternalPolicyService } from "../../abstractions/policy/policy.service.abstraction"; @@ -18,7 +17,6 @@ export class PolicyApiService implements PolicyApiServiceAbstraction { constructor( private policyService: InternalPolicyService, private apiService: ApiService, - private stateService: StateService, ) {} async getPolicy(organizationId: string, type: PolicyType): Promise { diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 24da0dd3da..7f079be45f 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -74,8 +74,6 @@ export abstract class StateService { setAutoConfirmFingerprints: (value: boolean, options?: StorageOptions) => Promise; getBiometricFingerprintValidated: (options?: StorageOptions) => Promise; setBiometricFingerprintValidated: (value: boolean, options?: StorageOptions) => Promise; - getBiometricUnlock: (options?: StorageOptions) => Promise; - setBiometricUnlock: (value: boolean, options?: StorageOptions) => Promise; getCanAccessPremium: (options?: StorageOptions) => Promise; getHasPremiumPersonally: (options?: StorageOptions) => Promise; setHasPremiumPersonally: (value: boolean, options?: StorageOptions) => Promise; @@ -330,6 +328,8 @@ export abstract class StateService { setKeyHash: (value: string, options?: StorageOptions) => Promise; getLastActive: (options?: StorageOptions) => Promise; setLastActive: (value: number, options?: StorageOptions) => Promise; + getLastSync: (options?: StorageOptions) => Promise; + setLastSync: (value: string, options?: StorageOptions) => Promise; getLocalData: (options?: StorageOptions) => Promise<{ [cipherId: string]: LocalData }>; setLocalData: ( value: { [cipherId: string]: LocalData }, diff --git a/libs/common/src/platform/biometrics/biometric-state.service.spec.ts b/libs/common/src/platform/biometrics/biometric-state.service.spec.ts index 44be94983f..54471a25cd 100644 --- a/libs/common/src/platform/biometrics/biometric-state.service.spec.ts +++ b/libs/common/src/platform/biometrics/biometric-state.service.spec.ts @@ -9,6 +9,7 @@ import { EncryptedString } from "../models/domain/enc-string"; import { BiometricStateService, DefaultBiometricStateService } from "./biometric-state.service"; import { + BIOMETRIC_UNLOCK_ENABLED, DISMISSED_REQUIRE_PASSWORD_ON_START_CALLOUT, ENCRYPTED_CLIENT_KEY_HALF, PROMPT_AUTOMATICALLY, @@ -35,33 +36,39 @@ describe("BiometricStateService", () => { }); describe("requirePasswordOnStart$", () => { - it("should track the requirePasswordOnStart state", async () => { + it("emits when the require password on start state changes", async () => { const state = stateProvider.activeUser.getFake(REQUIRE_PASSWORD_ON_START); - state.nextState(undefined); - - expect(await firstValueFrom(sut.requirePasswordOnStart$)).toBe(false); - state.nextState(true); expect(await firstValueFrom(sut.requirePasswordOnStart$)).toBe(true); }); + + it("emits false when the require password on start state is undefined", async () => { + const state = stateProvider.activeUser.getFake(REQUIRE_PASSWORD_ON_START); + state.nextState(undefined); + + expect(await firstValueFrom(sut.requirePasswordOnStart$)).toBe(false); + }); }); describe("encryptedClientKeyHalf$", () => { - it("should track the encryptedClientKeyHalf state", async () => { + it("emits when the encryptedClientKeyHalf state changes", async () => { const state = stateProvider.activeUser.getFake(ENCRYPTED_CLIENT_KEY_HALF); - state.nextState(undefined); - - expect(await firstValueFrom(sut.encryptedClientKeyHalf$)).toBe(null); - state.nextState(encryptedClientKeyHalf); expect(await firstValueFrom(sut.encryptedClientKeyHalf$)).toEqual(encClientKeyHalf); }); + + it("emits false when the encryptedClientKeyHalf state is undefined", async () => { + const state = stateProvider.activeUser.getFake(ENCRYPTED_CLIENT_KEY_HALF); + state.nextState(undefined); + + expect(await firstValueFrom(sut.encryptedClientKeyHalf$)).toBe(null); + }); }); describe("setEncryptedClientKeyHalf", () => { - it("should update the encryptedClientKeyHalf$", async () => { + it("updates encryptedClientKeyHalf$", async () => { await sut.setEncryptedClientKeyHalf(encClientKeyHalf); expect(await firstValueFrom(sut.encryptedClientKeyHalf$)).toEqual(encClientKeyHalf); @@ -69,13 +76,13 @@ describe("BiometricStateService", () => { }); describe("setRequirePasswordOnStart", () => { - it("should update the requirePasswordOnStart$", async () => { + it("updates the requirePasswordOnStart$", async () => { await sut.setRequirePasswordOnStart(true); expect(await firstValueFrom(sut.requirePasswordOnStart$)).toBe(true); }); - it("should remove the encryptedClientKeyHalf if the value is false", async () => { + it("removes the encryptedClientKeyHalf when the set value is false", async () => { await sut.setEncryptedClientKeyHalf(encClientKeyHalf, userId); await sut.setRequirePasswordOnStart(false); @@ -87,7 +94,7 @@ describe("BiometricStateService", () => { expect(keyHalfState.nextMock).toHaveBeenCalledWith(null); }); - it("should not remove the encryptedClientKeyHalf if the value is true", async () => { + it("does not remove the encryptedClientKeyHalf when the value is true", async () => { await sut.setEncryptedClientKeyHalf(encClientKeyHalf); await sut.setRequirePasswordOnStart(true); @@ -96,7 +103,7 @@ describe("BiometricStateService", () => { }); describe("getRequirePasswordOnStart", () => { - it("should return the requirePasswordOnStart value", async () => { + it("returns the requirePasswordOnStart state value", async () => { stateProvider.singleUser.mockFor(userId, REQUIRE_PASSWORD_ON_START.key, true); expect(await sut.getRequirePasswordOnStart(userId)).toBe(true); @@ -104,17 +111,17 @@ describe("BiometricStateService", () => { }); describe("require password on start callout", () => { - it("should be false when not set", async () => { + it("is false when not set", async () => { expect(await firstValueFrom(sut.dismissedRequirePasswordOnStartCallout$)).toBe(false); }); - it("should be true when set", async () => { + it("is true when set", async () => { await sut.setDismissedRequirePasswordOnStartCallout(); expect(await firstValueFrom(sut.dismissedRequirePasswordOnStartCallout$)).toBe(true); }); - it("should update disk state", async () => { + it("updates disk state when called", async () => { await sut.setDismissedRequirePasswordOnStartCallout(); expect( @@ -123,14 +130,14 @@ describe("BiometricStateService", () => { }); }); - describe("prompt cancelled", () => { - test("observable should be updated", async () => { + describe("setPromptCancelled", () => { + test("observable is updated", async () => { await sut.setPromptCancelled(); expect(await firstValueFrom(sut.promptCancelled$)).toBe(true); }); - it("should update state with set", async () => { + it("updates state", async () => { await sut.setPromptCancelled(); const nextMock = stateProvider.activeUser.getFake(PROMPT_CANCELLED).nextMock; @@ -139,14 +146,14 @@ describe("BiometricStateService", () => { }); }); - describe("prompt automatically", () => { - test("observable should be updated", async () => { + describe("setPromptAutomatically", () => { + test("observable is updated", async () => { await sut.setPromptAutomatically(true); expect(await firstValueFrom(sut.promptAutomatically$)).toBe(true); }); - it("should update state with setPromptAutomatically", async () => { + it("updates state", async () => { await sut.setPromptAutomatically(true); const nextMock = stateProvider.activeUser.getFake(PROMPT_AUTOMATICALLY).nextMock; @@ -154,4 +161,50 @@ describe("BiometricStateService", () => { expect(nextMock).toHaveBeenCalledTimes(1); }); }); + + describe("biometricUnlockEnabled$", () => { + it("emits when biometricUnlockEnabled state is updated", async () => { + const state = stateProvider.activeUser.getFake(BIOMETRIC_UNLOCK_ENABLED); + state.nextState(true); + + expect(await firstValueFrom(sut.biometricUnlockEnabled$)).toBe(true); + }); + + it("emits false when biometricUnlockEnabled state is undefined", async () => { + const state = stateProvider.activeUser.getFake(BIOMETRIC_UNLOCK_ENABLED); + state.nextState(undefined); + + expect(await firstValueFrom(sut.biometricUnlockEnabled$)).toBe(false); + }); + }); + + describe("setBiometricUnlockEnabled", () => { + it("updates biometricUnlockEnabled$", async () => { + await sut.setBiometricUnlockEnabled(true); + + expect(await firstValueFrom(sut.biometricUnlockEnabled$)).toBe(true); + }); + + it("updates state", async () => { + await sut.setBiometricUnlockEnabled(true); + + expect( + stateProvider.activeUser.getFake(BIOMETRIC_UNLOCK_ENABLED).nextMock, + ).toHaveBeenCalledWith([userId, true]); + }); + }); + + describe("getBiometricUnlockEnabled", () => { + it("returns biometricUnlockEnabled state for the given user", async () => { + stateProvider.singleUser.getFake(userId, BIOMETRIC_UNLOCK_ENABLED).nextState(true); + + expect(await sut.getBiometricUnlockEnabled(userId)).toBe(true); + }); + + it("returns false when the state is not set", async () => { + stateProvider.singleUser.getFake(userId, BIOMETRIC_UNLOCK_ENABLED).nextState(undefined); + + expect(await sut.getBiometricUnlockEnabled(userId)).toBe(false); + }); + }); }); diff --git a/libs/common/src/platform/biometrics/biometric-state.service.ts b/libs/common/src/platform/biometrics/biometric-state.service.ts index 7033184dab..b00090eb26 100644 --- a/libs/common/src/platform/biometrics/biometric-state.service.ts +++ b/libs/common/src/platform/biometrics/biometric-state.service.ts @@ -5,6 +5,7 @@ import { EncryptedString, EncString } from "../models/domain/enc-string"; import { ActiveUserState, StateProvider } from "../state"; import { + BIOMETRIC_UNLOCK_ENABLED, ENCRYPTED_CLIENT_KEY_HALF, REQUIRE_PASSWORD_ON_START, DISMISSED_REQUIRE_PASSWORD_ON_START_CALLOUT, @@ -13,6 +14,10 @@ import { } from "./biometric.state"; export abstract class BiometricStateService { + /** + * `true` if the currently active user has elected to store a biometric key to unlock their vault. + */ + biometricUnlockEnabled$: Observable; // used to be biometricUnlock /** * If the user has elected to require a password on first unlock of an application instance, this key will store the * encrypted client key half used to unlock the vault. @@ -52,6 +57,16 @@ export abstract class BiometricStateService { * @param value whether or not a password is required on first unlock after opening the application */ abstract setRequirePasswordOnStart(value: boolean): Promise; + /** + * Updates the biometric unlock enabled state for the currently active user. + * @param enabled whether or not to store a biometric key to unlock the vault + */ + abstract setBiometricUnlockEnabled(enabled: boolean): Promise; + /** + * Gets the biometric unlock enabled state for the given user. + * @param userId user Id to check + */ + abstract getBiometricUnlockEnabled(userId: UserId): Promise; abstract setEncryptedClientKeyHalf(encryptedKeyHalf: EncString, userId?: UserId): Promise; abstract getEncryptedClientKeyHalf(userId: UserId): Promise; abstract getRequirePasswordOnStart(userId: UserId): Promise; @@ -78,11 +93,13 @@ export abstract class BiometricStateService { } export class DefaultBiometricStateService implements BiometricStateService { + private biometricUnlockEnabledState: ActiveUserState; private requirePasswordOnStartState: ActiveUserState; private encryptedClientKeyHalfState: ActiveUserState; private dismissedRequirePasswordOnStartCalloutState: ActiveUserState; private promptCancelledState: ActiveUserState; private promptAutomaticallyState: ActiveUserState; + biometricUnlockEnabled$: Observable; encryptedClientKeyHalf$: Observable; requirePasswordOnStart$: Observable; dismissedRequirePasswordOnStartCallout$: Observable; @@ -90,6 +107,9 @@ export class DefaultBiometricStateService implements BiometricStateService { promptAutomatically$: Observable; constructor(private stateProvider: StateProvider) { + this.biometricUnlockEnabledState = this.stateProvider.getActive(BIOMETRIC_UNLOCK_ENABLED); + this.biometricUnlockEnabled$ = this.biometricUnlockEnabledState.state$.pipe(map(Boolean)); + this.requirePasswordOnStartState = this.stateProvider.getActive(REQUIRE_PASSWORD_ON_START); this.requirePasswordOnStart$ = this.requirePasswordOnStartState.state$.pipe( map((value) => !!value), @@ -104,12 +124,22 @@ export class DefaultBiometricStateService implements BiometricStateService { DISMISSED_REQUIRE_PASSWORD_ON_START_CALLOUT, ); this.dismissedRequirePasswordOnStartCallout$ = - this.dismissedRequirePasswordOnStartCalloutState.state$.pipe(map((v) => !!v)); + this.dismissedRequirePasswordOnStartCalloutState.state$.pipe(map(Boolean)); this.promptCancelledState = this.stateProvider.getActive(PROMPT_CANCELLED); - this.promptCancelled$ = this.promptCancelledState.state$.pipe(map((v) => !!v)); + this.promptCancelled$ = this.promptCancelledState.state$.pipe(map(Boolean)); this.promptAutomaticallyState = this.stateProvider.getActive(PROMPT_AUTOMATICALLY); - this.promptAutomatically$ = this.promptAutomaticallyState.state$.pipe(map((v) => !!v)); + this.promptAutomatically$ = this.promptAutomaticallyState.state$.pipe(map(Boolean)); + } + + async setBiometricUnlockEnabled(enabled: boolean): Promise { + await this.biometricUnlockEnabledState.update(() => enabled); + } + + async getBiometricUnlockEnabled(userId: UserId): Promise { + return await firstValueFrom( + this.stateProvider.getUser(userId, BIOMETRIC_UNLOCK_ENABLED).state$.pipe(map(Boolean)), + ); } async setRequirePasswordOnStart(value: boolean): Promise { diff --git a/libs/common/src/platform/biometrics/biometric.state.spec.ts b/libs/common/src/platform/biometrics/biometric.state.spec.ts index c868a5b927..4f79f8da73 100644 --- a/libs/common/src/platform/biometrics/biometric.state.spec.ts +++ b/libs/common/src/platform/biometrics/biometric.state.spec.ts @@ -2,6 +2,7 @@ import { EncryptedString } from "../models/domain/enc-string"; import { KeyDefinition } from "../state"; import { + BIOMETRIC_UNLOCK_ENABLED, DISMISSED_REQUIRE_PASSWORD_ON_START_CALLOUT, ENCRYPTED_CLIENT_KEY_HALF, PROMPT_AUTOMATICALLY, @@ -15,22 +16,20 @@ describe.each([ [PROMPT_CANCELLED, true], [PROMPT_AUTOMATICALLY, true], [REQUIRE_PASSWORD_ON_START, true], + [BIOMETRIC_UNLOCK_ENABLED, "test"], ])( "deserializes state %s", ( ...args: [KeyDefinition, EncryptedString] | [KeyDefinition, boolean] ) => { + function testDeserialization(keyDefinition: KeyDefinition, state: T) { + const deserialized = keyDefinition.deserializer(JSON.parse(JSON.stringify(state))); + expect(deserialized).toEqual(state); + } + it("should deserialize state", () => { const [keyDefinition, state] = args; - // Need to type check to avoid TS error due to array values being unions instead of guaranteed tuple pairs - if (typeof state === "boolean") { - const deserialized = keyDefinition.deserializer(JSON.parse(JSON.stringify(state))); - expect(deserialized).toEqual(state); - return; - } else { - const deserialized = keyDefinition.deserializer(JSON.parse(JSON.stringify(state))); - expect(deserialized).toEqual(state); - } + testDeserialization(keyDefinition, state); }); }, ); diff --git a/libs/common/src/platform/biometrics/biometric.state.ts b/libs/common/src/platform/biometrics/biometric.state.ts index 84a4c13a5f..8796366c88 100644 --- a/libs/common/src/platform/biometrics/biometric.state.ts +++ b/libs/common/src/platform/biometrics/biometric.state.ts @@ -1,6 +1,17 @@ import { EncryptedString } from "../models/domain/enc-string"; import { KeyDefinition, BIOMETRIC_SETTINGS_DISK } from "../state"; +/** + * Indicates whether the user elected to store a biometric key to unlock their vault. + */ +export const BIOMETRIC_UNLOCK_ENABLED = new KeyDefinition( + BIOMETRIC_SETTINGS_DISK, + "biometricUnlockEnabled", + { + deserializer: (obj) => obj, + }, +); + /** * Boolean indicating the user has elected to require a password to use their biometric key upon starting the application. * diff --git a/libs/common/src/platform/models/domain/account.ts b/libs/common/src/platform/models/domain/account.ts index 5fa1039fe3..7ab98eec7d 100644 --- a/libs/common/src/platform/models/domain/account.ts +++ b/libs/common/src/platform/models/domain/account.ts @@ -181,6 +181,7 @@ export class AccountProfile { forceSetPasswordReason?: ForceSetPasswordReason; hasPremiumPersonally?: boolean; hasPremiumFromOrganization?: boolean; + lastSync?: string; userId?: string; usesKeyConnector?: boolean; keyHash?: string; @@ -200,7 +201,6 @@ export class AccountProfile { export class AccountSettings { autoConfirmFingerPrints?: boolean; - biometricUnlock?: boolean; defaultUriMatch?: UriMatchType; disableGa?: boolean; dontShowCardsCurrentTab?: boolean; diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index 3db3809d24..cb3b3c8c87 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -378,24 +378,6 @@ export class StateService< ); } - async getBiometricUnlock(options?: StorageOptions): Promise { - return ( - (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) - ?.settings?.biometricUnlock ?? false - ); - } - - async setBiometricUnlock(value: boolean, options?: StorageOptions): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultOnDiskOptions()), - ); - account.settings.biometricUnlock = value; - await this.saveAccount( - account, - this.reconcileOptions(options, await this.defaultOnDiskOptions()), - ); - } - async getCanAccessPremium(options?: StorageOptions): Promise { if (!(await this.getIsAuthenticated(options))) { return false; @@ -1637,6 +1619,23 @@ export class StateService< await this.storageService.save(keys.accountActivity, accountActivity, options); } + async getLastSync(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())) + )?.profile?.lastSync; + } + + async setLastSync(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()), + ); + account.profile.lastSync = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()), + ); + } + async getLocalData(options?: StorageOptions): Promise<{ [cipherId: string]: LocalData }> { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index 03269b6808..c5be07023e 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -44,8 +44,6 @@ export const BILLING_DISK = new StateDefinition("billing", "disk"); export const FOLDER_DISK = new StateDefinition("folder", "disk", { web: "memory" }); -export const SYNC_STATE = new StateDefinition("sync", "disk", { web: "memory" }); - export const VAULT_SETTINGS_DISK = new StateDefinition("vaultSettings", "disk", { web: "disk-local", }); diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts index 46d3778348..f1881080d9 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.spec.ts @@ -1,5 +1,5 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, of } from "rxjs"; import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction"; import { Policy } from "../../admin-console/models/domain/policy"; @@ -7,6 +7,7 @@ import { TokenService } from "../../auth/abstractions/token.service"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; import { CryptoService } from "../../platform/abstractions/crypto.service"; import { StateService } from "../../platform/abstractions/state.service"; +import { BiometricStateService } from "../../platform/biometrics/biometric-state.service"; import { AccountDecryptionOptions } from "../../platform/models/domain/account"; import { EncString } from "../../platform/models/domain/enc-string"; @@ -17,6 +18,7 @@ describe("VaultTimeoutSettingsService", () => { let tokenService: MockProxy; let policyService: MockProxy; let stateService: MockProxy; + const biometricStateService = mock(); let service: VaultTimeoutSettingsService; beforeEach(() => { @@ -29,7 +31,14 @@ describe("VaultTimeoutSettingsService", () => { tokenService, policyService, stateService, + biometricStateService, ); + + biometricStateService.biometricUnlockEnabled$ = of(false); + }); + + afterEach(() => { + jest.resetAllMocks(); }); describe("availableVaultTimeoutActions$", () => { @@ -66,7 +75,7 @@ describe("VaultTimeoutSettingsService", () => { }); it("contains Lock when the user has biometrics configured", async () => { - stateService.getBiometricUnlock.mockResolvedValue(true); + biometricStateService.biometricUnlockEnabled$ = of(true); const result = await firstValueFrom(service.availableVaultTimeoutActions$()); @@ -79,7 +88,7 @@ describe("VaultTimeoutSettingsService", () => { ); stateService.getPinKeyEncryptedUserKey.mockResolvedValue(null); stateService.getProtectedPin.mockResolvedValue(null); - stateService.getBiometricUnlock.mockResolvedValue(false); + biometricStateService.biometricUnlockEnabled$ = of(false); const result = await firstValueFrom(service.availableVaultTimeoutActions$()); @@ -127,7 +136,7 @@ describe("VaultTimeoutSettingsService", () => { `( "returns $expected when policy is $policy, has unlock method is $unlockMethod, and user preference is $userPreference", async ({ unlockMethod, policy, userPreference, expected }) => { - stateService.getBiometricUnlock.mockResolvedValue(unlockMethod); + biometricStateService.biometricUnlockEnabled$ = of(unlockMethod); stateService.getAccountDecryptionOptions.mockResolvedValue( new AccountDecryptionOptions({ hasMasterPassword: false }), ); diff --git a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts index 6bb7c73f6a..e84d561fe6 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout-settings.service.ts @@ -1,4 +1,4 @@ -import { defer } from "rxjs"; +import { defer, firstValueFrom } from "rxjs"; import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction"; @@ -7,6 +7,8 @@ import { TokenService } from "../../auth/abstractions/token.service"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; import { CryptoService } from "../../platform/abstractions/crypto.service"; import { StateService } from "../../platform/abstractions/state.service"; +import { BiometricStateService } from "../../platform/biometrics/biometric-state.service"; +import { UserId } from "../../types/guid"; /** * - DISABLED: No Pin set @@ -21,6 +23,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA private tokenService: TokenService, private policyService: PolicyService, private stateService: StateService, + private biometricStateService: BiometricStateService, ) {} async setVaultTimeoutOptions(timeout: number, action: VaultTimeoutAction): Promise { @@ -74,7 +77,11 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA } async isBiometricLockSet(userId?: string): Promise { - return await this.stateService.getBiometricUnlock({ userId }); + const biometricUnlockPromise = + userId == null + ? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$) + : this.biometricStateService.getBiometricUnlockEnabled(userId as UserId); + return await biometricUnlockPromise; } async getVaultTimeout(userId?: string): Promise { diff --git a/libs/common/src/state-migrations/migrate.ts b/libs/common/src/state-migrations/migrate.ts index bec8e0241f..7ed50c4206 100644 --- a/libs/common/src/state-migrations/migrate.ts +++ b/libs/common/src/state-migrations/migrate.ts @@ -20,7 +20,9 @@ import { CollapsedGroupingsMigrator } from "./migrations/22-move-collapsed-group import { MoveBiometricPromptsToStateProviders } from "./migrations/23-move-biometric-prompts-to-state-providers"; import { SmOnboardingTasksMigrator } from "./migrations/24-move-sm-onboarding-key-to-state-providers"; import { ClearClipboardDelayMigrator } from "./migrations/25-move-clear-clipboard-to-autofill-settings-state-provider"; -import { BadgeSettingsMigrator } from "./migrations/26-move-badge-settings-to-state-providers"; +import { RevertLastSyncMigrator } from "./migrations/26-revert-move-last-sync-to-state-provider"; +import { BadgeSettingsMigrator } from "./migrations/27-move-badge-settings-to-state-providers"; +import { MoveBiometricUnlockToStateProviders } from "./migrations/28-move-biometric-unlock-to-state-providers"; import { FixPremiumMigrator } from "./migrations/3-fix-premium"; import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked"; import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys"; @@ -31,7 +33,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting import { MinVersionMigrator } from "./migrations/min-version"; export const MIN_VERSION = 2; -export const CURRENT_VERSION = 26; +export const CURRENT_VERSION = 28; export type MinVersion = typeof MIN_VERSION; export function createMigrationBuilder() { @@ -60,7 +62,9 @@ export function createMigrationBuilder() { .with(MoveBiometricPromptsToStateProviders, 22, 23) .with(SmOnboardingTasksMigrator, 23, 24) .with(ClearClipboardDelayMigrator, 24, 25) - .with(BadgeSettingsMigrator, 25, CURRENT_VERSION); + .with(RevertLastSyncMigrator, 25, 26) + .with(BadgeSettingsMigrator, 26, 27) + .with(MoveBiometricUnlockToStateProviders, 27, CURRENT_VERSION); } export async function currentVersion( diff --git a/libs/common/src/state-migrations/migrations/15-move-folder-state-to-state-provider.spec.ts b/libs/common/src/state-migrations/migrations/15-move-folder-state-to-state-provider.spec.ts index d990bf8f14..05e791f76b 100644 --- a/libs/common/src/state-migrations/migrations/15-move-folder-state-to-state-provider.spec.ts +++ b/libs/common/src/state-migrations/migrations/15-move-folder-state-to-state-provider.spec.ts @@ -1,4 +1,4 @@ -import { any, MockProxy } from "jest-mock-extended"; +import { MockProxy, any } from "jest-mock-extended"; import { MigrationHelper } from "../migration-helper"; import { mockMigrationHelper } from "../migration-helper.spec"; diff --git a/libs/common/src/state-migrations/migrations/26-revert-move-last-sync-to-state-provider.spec.ts b/libs/common/src/state-migrations/migrations/26-revert-move-last-sync-to-state-provider.spec.ts new file mode 100644 index 0000000000..19fc713338 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/26-revert-move-last-sync-to-state-provider.spec.ts @@ -0,0 +1,112 @@ +import { any, MockProxy } from "jest-mock-extended"; + +import { MigrationHelper } from "../migration-helper"; +import { mockMigrationHelper } from "../migration-helper.spec"; + +import { RevertLastSyncMigrator } from "./26-revert-move-last-sync-to-state-provider"; + +function rollbackJSON() { + return { + global: { + otherStuff: "otherStuff1", + }, + authenticatedAccounts: ["user-1", "user-2"], + "user-1": { + profile: { + lastSync: "2024-01-24T00:00:00.000Z", + otherStuff: "otherStuff4", + }, + otherStuff: "otherStuff5", + }, + }; +} + +function exampleJSON() { + return { + "user_user-1_sync_lastSync": "2024-01-24T00:00:00.000Z", + "user_user-2_sync_lastSync": null as any, + global: { + otherStuff: "otherStuff1", + }, + authenticatedAccounts: ["user-1", "user-2"], + "user-1": { + profile: { + lastSync: "2024-01-24T00:00:00.000Z", + otherStuff: "otherStuff4", + }, + otherStuff: "otherStuff5", + }, + }; +} + +describe("LastSyncMigrator", () => { + let helper: MockProxy; + let sut: RevertLastSyncMigrator; + + const keyDefinitionLike = { + key: "lastSync", + stateDefinition: { + name: "sync", + }, + }; + + describe("rollback", () => { + beforeEach(() => { + helper = mockMigrationHelper(rollbackJSON(), 26); + sut = new RevertLastSyncMigrator(25, 26); + }); + + it("should remove lastSync from all accounts", async () => { + await sut.rollback(helper); + expect(helper.set).toHaveBeenCalledWith("user-1", { + profile: { + otherStuff: "otherStuff4", + }, + otherStuff: "otherStuff5", + }); + }); + + it("should set lastSync provider value for each account", async () => { + await sut.rollback(helper); + + expect(helper.setToUser).toHaveBeenCalledWith( + "user-1", + keyDefinitionLike, + "2024-01-24T00:00:00.000Z", + ); + + expect(helper.setToUser).toHaveBeenCalledWith("user-2", keyDefinitionLike, null); + }); + }); + + describe("migrate", () => { + beforeEach(() => { + helper = mockMigrationHelper(exampleJSON(), 25); + sut = new RevertLastSyncMigrator(25, 26); + }); + + it.each(["user-1", "user-2"])("should null out new values", async (userId) => { + await sut.migrate(helper); + + expect(helper.setToUser).toHaveBeenCalledWith(userId, keyDefinitionLike, null); + }); + + it("should add lastSync back to accounts", async () => { + await sut.migrate(helper); + + expect(helper.set).toHaveBeenCalledWith("user-1", { + profile: { + lastSync: "2024-01-24T00:00:00.000Z", + otherStuff: "otherStuff4", + }, + otherStuff: "otherStuff5", + }); + }); + + it("should not try to restore values to missing accounts", async () => { + await sut.rollback(helper); + + expect(helper.set).not.toHaveBeenCalledWith("user-2", any()); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/26-revert-move-last-sync-to-state-provider.ts b/libs/common/src/state-migrations/migrations/26-revert-move-last-sync-to-state-provider.ts new file mode 100644 index 0000000000..ef9c1b37fa --- /dev/null +++ b/libs/common/src/state-migrations/migrations/26-revert-move-last-sync-to-state-provider.ts @@ -0,0 +1,47 @@ +import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; +import { Migrator } from "../migrator"; + +type ExpectedAccountType = { + profile?: { + lastSync?: string; + }; +}; + +const LAST_SYNC_KEY: KeyDefinitionLike = { + key: "lastSync", + stateDefinition: { + name: "sync", + }, +}; + +export class RevertLastSyncMigrator extends Migrator<25, 26> { + async rollback(helper: MigrationHelper): Promise { + const accounts = await helper.getAccounts(); + async function rollbackAccount(userId: string, account: ExpectedAccountType): Promise { + const value = account?.profile?.lastSync; + await helper.setToUser(userId, LAST_SYNC_KEY, value ?? null); + if (value != null) { + delete account.profile.lastSync; + await helper.set(userId, account); + } + } + + await Promise.all([...accounts.map(({ userId, account }) => rollbackAccount(userId, account))]); + } + async migrate(helper: MigrationHelper): Promise { + const accounts = await helper.getAccounts(); + + async function migrateAccount(userId: string, account: ExpectedAccountType): Promise { + const value = await helper.getFromUser(userId, LAST_SYNC_KEY); + if (account) { + account.profile = Object.assign(account.profile ?? {}, { + lastSync: value, + }); + await helper.set(userId, account); + } + await helper.setToUser(userId, LAST_SYNC_KEY, null); + } + + await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]); + } +} diff --git a/libs/common/src/state-migrations/migrations/26-move-badge-settings-to-state-providers.spec.ts b/libs/common/src/state-migrations/migrations/27-move-badge-settings-to-state-providers.spec.ts similarity index 93% rename from libs/common/src/state-migrations/migrations/26-move-badge-settings-to-state-providers.spec.ts rename to libs/common/src/state-migrations/migrations/27-move-badge-settings-to-state-providers.spec.ts index 9e6ae77041..dbc15ea94b 100644 --- a/libs/common/src/state-migrations/migrations/26-move-badge-settings-to-state-providers.spec.ts +++ b/libs/common/src/state-migrations/migrations/27-move-badge-settings-to-state-providers.spec.ts @@ -3,7 +3,7 @@ import { any, MockProxy } from "jest-mock-extended"; import { StateDefinitionLike, MigrationHelper } from "../migration-helper"; import { mockMigrationHelper } from "../migration-helper.spec"; -import { BadgeSettingsMigrator } from "./26-move-badge-settings-to-state-providers"; +import { BadgeSettingsMigrator } from "./27-move-badge-settings-to-state-providers"; function exampleJSON() { return { @@ -77,8 +77,8 @@ describe("BadgeSettingsMigrator", () => { describe("migrate", () => { beforeEach(() => { - helper = mockMigrationHelper(exampleJSON(), 25); - sut = new BadgeSettingsMigrator(25, 26); + helper = mockMigrationHelper(exampleJSON(), 26); + sut = new BadgeSettingsMigrator(26, 27); }); it("should remove disableBadgeCounter setting from all accounts", async () => { @@ -117,8 +117,8 @@ describe("BadgeSettingsMigrator", () => { describe("rollback", () => { beforeEach(() => { - helper = mockMigrationHelper(rollbackJSON(), 26); - sut = new BadgeSettingsMigrator(25, 26); + helper = mockMigrationHelper(rollbackJSON(), 27); + sut = new BadgeSettingsMigrator(26, 27); }); it("should null out new values for each account", async () => { diff --git a/libs/common/src/state-migrations/migrations/26-move-badge-settings-to-state-providers.ts b/libs/common/src/state-migrations/migrations/27-move-badge-settings-to-state-providers.ts similarity index 97% rename from libs/common/src/state-migrations/migrations/26-move-badge-settings-to-state-providers.ts rename to libs/common/src/state-migrations/migrations/27-move-badge-settings-to-state-providers.ts index 090cb5a790..376e5aefea 100644 --- a/libs/common/src/state-migrations/migrations/26-move-badge-settings-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/27-move-badge-settings-to-state-providers.ts @@ -14,7 +14,7 @@ const enableBadgeCounterKeyDefinition: KeyDefinitionLike = { key: "enableBadgeCounter", }; -export class BadgeSettingsMigrator extends Migrator<25, 26> { +export class BadgeSettingsMigrator extends Migrator<26, 27> { async migrate(helper: MigrationHelper): Promise { // account state (e.g. account settings -> state provider framework keys) const accounts = await helper.getAccounts(); diff --git a/libs/common/src/state-migrations/migrations/28-move-biometric-unlock-to-state-providers.spec.ts b/libs/common/src/state-migrations/migrations/28-move-biometric-unlock-to-state-providers.spec.ts new file mode 100644 index 0000000000..7ef242e8d0 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/28-move-biometric-unlock-to-state-providers.spec.ts @@ -0,0 +1,120 @@ +import { MockProxy, any } from "jest-mock-extended"; + +import { MigrationHelper } from "../migration-helper"; +import { mockMigrationHelper } from "../migration-helper.spec"; + +import { + BIOMETRIC_UNLOCK_ENABLED, + MoveBiometricUnlockToStateProviders, +} from "./28-move-biometric-unlock-to-state-providers"; + +function exampleJSON() { + return { + global: { + otherStuff: "otherStuff1", + }, + authenticatedAccounts: ["user-1", "user-2", "user-3"], + "user-1": { + settings: { + biometricUnlock: true, + otherStuff: "otherStuff2", + }, + otherStuff: "otherStuff3", + }, + "user-2": { + otherStuff: "otherStuff4", + }, + }; +} + +function rollbackJSON() { + return { + "user_user-1_biometricSettings_biometricUnlockEnabled": true, + global: { + otherStuff: "otherStuff1", + }, + authenticatedAccounts: ["user-1", "user-2", "user-3"], + "user-1": { + settings: { + otherStuff: "otherStuff2", + }, + otherStuff: "otherStuff3", + }, + "user-2": { + otherStuff: "otherStuff4", + }, + }; +} + +describe("MoveBiometricPromptsToStateProviders migrator", () => { + let helper: MockProxy; + let sut: MoveBiometricUnlockToStateProviders; + + describe("migrate", () => { + beforeEach(() => { + helper = mockMigrationHelper(exampleJSON(), 27); + sut = new MoveBiometricUnlockToStateProviders(27, 28); + }); + + it("removes biometricUnlock from all accounts", async () => { + await sut.migrate(helper); + expect(helper.set).toHaveBeenCalledTimes(2); + expect(helper.set).toHaveBeenCalledWith("user-1", { + settings: { + otherStuff: "otherStuff2", + }, + otherStuff: "otherStuff3", + }); + expect(helper.set).toHaveBeenCalledWith("user-2", { + otherStuff: "otherStuff4", + }); + }); + + it("sets biometricUnlock value for account that have it", async () => { + await sut.migrate(helper); + + expect(helper.setToUser).toHaveBeenCalledWith("user-1", BIOMETRIC_UNLOCK_ENABLED, true); + }); + + it("should not call extra setToUser", async () => { + await sut.migrate(helper); + + expect(helper.setToUser).toHaveBeenCalledTimes(1); + }); + }); + + describe("rollback", () => { + beforeEach(() => { + helper = mockMigrationHelper(rollbackJSON(), 28); + sut = new MoveBiometricUnlockToStateProviders(27, 28); + }); + + it("nulls out new values", async () => { + await sut.rollback(helper); + + expect(helper.setToUser).toHaveBeenCalledWith("user-1", BIOMETRIC_UNLOCK_ENABLED, null); + }); + + it("adds explicit value back to accounts", async () => { + await sut.rollback(helper); + + expect(helper.set).toHaveBeenCalledTimes(1); + expect(helper.set).toHaveBeenCalledWith("user-1", { + settings: { + biometricUnlock: true, + otherStuff: "otherStuff2", + }, + otherStuff: "otherStuff3", + }); + }); + + it.each(["user-2", "user-3"])( + "does not restore values when accounts are not present", + async (userId) => { + await sut.rollback(helper); + + expect(helper.set).not.toHaveBeenCalledWith(userId, any()); + }, + ); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/28-move-biometric-unlock-to-state-providers.ts b/libs/common/src/state-migrations/migrations/28-move-biometric-unlock-to-state-providers.ts new file mode 100644 index 0000000000..ae4f86e3d5 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/28-move-biometric-unlock-to-state-providers.ts @@ -0,0 +1,58 @@ +import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; +import { Migrator } from "../migrator"; + +type ExpectedAccountType = { + settings?: { + biometricUnlock?: boolean; + }; +}; + +export const BIOMETRIC_UNLOCK_ENABLED: KeyDefinitionLike = { + key: "biometricUnlockEnabled", + stateDefinition: { name: "biometricSettings" }, +}; + +export class MoveBiometricUnlockToStateProviders extends Migrator<27, 28> { + async migrate(helper: MigrationHelper): Promise { + const legacyAccounts = await helper.getAccounts(); + + await Promise.all( + legacyAccounts.map(async ({ userId, account }) => { + if (account == null) { + return; + } + // Move account data + if (account?.settings?.biometricUnlock != null) { + await helper.setToUser( + userId, + BIOMETRIC_UNLOCK_ENABLED, + account.settings.biometricUnlock, + ); + } + + // Delete old account data + delete account?.settings?.biometricUnlock; + await helper.set(userId, account); + }), + ); + } + + async rollback(helper: MigrationHelper): Promise { + async function rollbackUser(userId: string, account: ExpectedAccountType) { + const biometricUnlock = await helper.getFromUser(userId, BIOMETRIC_UNLOCK_ENABLED); + + if (biometricUnlock != null) { + account ??= {}; + account.settings ??= {}; + + account.settings.biometricUnlock = biometricUnlock; + await helper.setToUser(userId, BIOMETRIC_UNLOCK_ENABLED, null); + await helper.set(userId, account); + } + } + + const accounts = await helper.getAccounts(); + + await Promise.all(accounts.map(({ userId, account }) => rollbackUser(userId, account))); + } +} diff --git a/libs/common/src/vault/abstractions/sync/sync.service.abstraction.ts b/libs/common/src/vault/abstractions/sync/sync.service.abstraction.ts index a7c23cb591..cfe7331755 100644 --- a/libs/common/src/vault/abstractions/sync/sync.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/sync/sync.service.abstraction.ts @@ -1,18 +1,14 @@ -import { Observable } from "rxjs"; - import { SyncCipherNotification, SyncFolderNotification, SyncSendNotification, } from "../../../models/response/notification.response"; -import { UserId } from "../../../types/guid"; export abstract class SyncService { syncInProgress: boolean; - lastSync$: Observable; getLastSync: () => Promise; - setLastSync: (date: Date, userId?: UserId) => Promise; + setLastSync: (date: Date, userId?: string) => Promise; fullSync: (forceSync: boolean, allowThrowOnError?: boolean) => Promise; syncUpsertFolder: (notification: SyncFolderNotification, isEdit: boolean) => Promise; syncDeleteFolder: (notification: SyncFolderNotification) => Promise; diff --git a/libs/common/src/vault/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/vault/services/fido2/fido2-authenticator.service.spec.ts index c5cf827561..a86dadab8f 100644 --- a/libs/common/src/vault/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/vault/services/fido2/fido2-authenticator.service.spec.ts @@ -656,14 +656,14 @@ describe("FidoAuthenticatorService", () => { beforeEach(init); /** Spec: Increment the credential associated signature counter */ - it("should increment counter", async () => { + it("should increment counter and save to server when stored counter is larger than zero", async () => { const encrypted = Symbol(); cipherService.encrypt.mockResolvedValue(encrypted as any); + ciphers[0].login.fido2Credentials[0].counter = 9000; await authenticator.getAssertion(params, tab); expect(cipherService.updateWithServer).toHaveBeenCalledWith(encrypted); - expect(cipherService.encrypt).toHaveBeenCalledWith( expect.objectContaining({ id: ciphers[0].id, @@ -678,6 +678,17 @@ describe("FidoAuthenticatorService", () => { ); }); + /** Spec: Authenticators that do not implement a signature counter leave the signCount in the authenticator data constant at zero. */ + it("should not save to server when stored counter is zero", async () => { + const encrypted = Symbol(); + cipherService.encrypt.mockResolvedValue(encrypted as any); + ciphers[0].login.fido2Credentials[0].counter = 0; + + await authenticator.getAssertion(params, tab); + + expect(cipherService.updateWithServer).not.toHaveBeenCalled(); + }); + it("should return an assertion result", async () => { const result = await authenticator.getAssertion(params, tab); diff --git a/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts b/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts index f3112eb350..e84f7add92 100644 --- a/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/vault/services/fido2/fido2-authenticator.service.ts @@ -257,14 +257,19 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr const selectedFido2Credential = selectedCipher.login.fido2Credentials[0]; const selectedCredentialId = selectedFido2Credential.credentialId; - ++selectedFido2Credential.counter; + if (selectedFido2Credential.counter > 0) { + ++selectedFido2Credential.counter; + } selectedCipher.localData = { ...selectedCipher.localData, lastUsedDate: new Date().getTime(), }; - const encrypted = await this.cipherService.encrypt(selectedCipher); - await this.cipherService.updateWithServer(encrypted); + + if (selectedFido2Credential.counter > 0) { + const encrypted = await this.cipherService.encrypt(selectedCipher); + await this.cipherService.updateWithServer(encrypted); + } const authenticatorData = await generateAuthData({ rpId: selectedFido2Credential.rpId, diff --git a/libs/common/src/vault/services/sync/sync.service.ts b/libs/common/src/vault/services/sync/sync.service.ts index 676fdf65ff..c0105af758 100644 --- a/libs/common/src/vault/services/sync/sync.service.ts +++ b/libs/common/src/vault/services/sync/sync.service.ts @@ -1,5 +1,3 @@ -import { firstValueFrom, map } from "rxjs"; - import { ApiService } from "../../../abstractions/api.service"; import { SettingsService } from "../../../abstractions/settings.service"; import { InternalOrganizationServiceAbstraction } from "../../../admin-console/abstractions/organization/organization.service.abstraction"; @@ -25,12 +23,10 @@ import { MessagingService } from "../../../platform/abstractions/messaging.servi import { StateService } from "../../../platform/abstractions/state.service"; import { sequentialize } from "../../../platform/misc/sequentialize"; import { AccountDecryptionOptions } from "../../../platform/models/domain/account"; -import { KeyDefinition, StateProvider, SYNC_STATE } from "../../../platform/state"; import { SendData } from "../../../tools/send/models/data/send.data"; import { SendResponse } from "../../../tools/send/models/response/send.response"; import { SendApiService } from "../../../tools/send/services/send-api.service.abstraction"; import { InternalSendService } from "../../../tools/send/services/send.service.abstraction"; -import { UserId } from "../../../types/guid"; import { CipherService } from "../../../vault/abstractions/cipher.service"; import { FolderApiServiceAbstraction } from "../../../vault/abstractions/folder/folder-api.service.abstraction"; import { InternalFolderService } from "../../../vault/abstractions/folder/folder.service.abstraction"; @@ -43,22 +39,8 @@ import { CollectionService } from "../../abstractions/collection.service"; import { CollectionData } from "../../models/data/collection.data"; import { CollectionDetailsResponse } from "../../models/response/collection.response"; -const LAST_SYNC_KEY = new KeyDefinition(SYNC_STATE, "lastSync", { - deserializer: (value) => value, -}); - export class SyncService implements SyncServiceAbstraction { - private lastSyncState = this.stateProvider.getActive(LAST_SYNC_KEY); - syncInProgress = false; - lastSync$ = this.lastSyncState.state$.pipe( - map((value) => { - if (value == null) { - return null; - } - return new Date(value); - }), - ); constructor( private apiService: ApiService, @@ -77,20 +59,24 @@ export class SyncService implements SyncServiceAbstraction { private folderApiService: FolderApiServiceAbstraction, private organizationService: InternalOrganizationServiceAbstraction, private sendApiService: SendApiService, - private stateProvider: StateProvider, private logoutCallback: (expired: boolean) => Promise, ) {} - async getLastSync(): Promise { - return await firstValueFrom(this.lastSync$); + async getLastSync(): Promise { + if ((await this.stateService.getUserId()) == null) { + return null; + } + + const lastSync = await this.stateService.getLastSync(); + if (lastSync) { + return new Date(lastSync); + } + + return null; } - async setLastSync(date: Date, userId?: UserId): Promise { - if (userId !== undefined) { - await this.stateProvider.getUser(userId, LAST_SYNC_KEY).update(() => date.toJSON()); - } else { - await this.lastSyncState.update(() => date.toJSON()); - } + async setLastSync(date: Date, userId?: string): Promise { + await this.stateService.setLastSync(date.toJSON(), { userId: userId }); } @sequentialize(() => "fullSync") diff --git a/libs/components/src/toggle-group/toggle-group.stories.ts b/libs/components/src/toggle-group/toggle-group.stories.ts index f150449630..8fdd22efe3 100644 --- a/libs/components/src/toggle-group/toggle-group.stories.ts +++ b/libs/components/src/toggle-group/toggle-group.stories.ts @@ -30,23 +30,15 @@ type Story = StoryObj; export const Default: Story = { render: (args) => ({ props: args, - template: ` + template: /* HTML */ ` - - All 3 - + All 3 - - Invited - + Invited - - Accepted 2 - + Accepted 2 - - Deactivated - + Deactivated `, }), diff --git a/libs/components/src/toggle-group/toggle.component.ts b/libs/components/src/toggle-group/toggle.component.ts index d12da7faa1..55c678d017 100644 --- a/libs/components/src/toggle-group/toggle.component.ts +++ b/libs/components/src/toggle-group/toggle.component.ts @@ -70,10 +70,8 @@ export class ToggleComponent { // Fix for bootstrap styles that add bottom margin "!tw-mb-0", - // Fix for badge being pushed slightly lower when inside a button. - // Inspired by bootstrap, which does the same. - "[&>[bitBadge]]:tw-relative", - "[&>[bitBadge]]:tw--top-px", + // Fix for badge being slightly off center vertically + "[&>[bitBadge]]:tw-mt-px", ]; } diff --git a/package-lock.json b/package-lock.json index 90c1150820..f6623bee44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -114,13 +114,13 @@ "@types/node-ipc": "9.2.0", "@types/papaparse": "5.3.14", "@types/proper-lockfile": "4.1.4", - "@types/react": "16.14.54", + "@types/react": "16.14.57", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.4", "@typescript-eslint/eslint-plugin": "6.21.0", "@typescript-eslint/parser": "6.21.0", "@webcomponents/custom-elements": "1.6.0", - "autoprefixer": "10.4.16", + "autoprefixer": "10.4.18", "base64-loader": "1.0.0", "chromatic": "10.9.6", "clean-webpack-plugin": "4.0.0", @@ -128,7 +128,7 @@ "copy-webpack-plugin": "11.0.0", "cross-env": "7.0.3", "css-loader": "6.8.1", - "electron": "28.2.4", + "electron": "28.2.5", "electron-builder": "24.9.1", "electron-log": "5.0.1", "electron-reload": "2.0.0-alpha.1", @@ -173,7 +173,7 @@ "sass-loader": "13.3.2", "storybook": "7.6.17", "style-loader": "3.3.3", - "tailwindcss": "3.3.5", + "tailwindcss": "3.4.1", "ts-jest": "29.1.2", "ts-loader": "9.5.1", "tsconfig-paths-webpack-plugin": "4.1.0", @@ -11344,9 +11344,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "16.14.54", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.54.tgz", - "integrity": "sha512-54MOeVbxTlC8U6XBy2sLhLaHg/QGP0gPEWIpl1E5tNTJDz/SdFktT3OuvAfKxpSXATUmKXDozHvxbT3XohJgDQ==", + "version": "16.14.57", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.57.tgz", + "integrity": "sha512-fuNq/GV1a6GgqSuVuC457vYeTbm4E1CUBQVZwSPxqYnRhIzSXCJ1gGqyv+PKhqLyfbKCga9dXHJDzv+4XE41fw==", "dev": true, "dependencies": { "@types/prop-types": "*", @@ -13874,9 +13874,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", + "version": "10.4.18", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz", + "integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==", "dev": true, "funding": [ { @@ -13893,9 +13893,9 @@ } ], "dependencies": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001591", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -13910,38 +13910,6 @@ "postcss": "^8.1.0" } }, - "node_modules/autoprefixer/node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -15267,9 +15235,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001588", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz", - "integrity": "sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==", + "version": "1.0.30001591", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz", + "integrity": "sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==", "funding": [ { "type": "opencollective", @@ -17736,9 +17704,9 @@ } }, "node_modules/electron": { - "version": "28.2.4", - "resolved": "https://registry.npmjs.org/electron/-/electron-28.2.4.tgz", - "integrity": "sha512-XrSsqKCmpGwz1pqzmdLhdGonYvatDXor60rBd+TfURMQQRSxyTaSwrds9kV0aab9YRpeYUH6NJDAOwBhQYDDkA==", + "version": "28.2.5", + "resolved": "https://registry.npmjs.org/electron/-/electron-28.2.5.tgz", + "integrity": "sha512-qlvQkDNVAzN647NpiJJw7GYJqE0NwK4+1evkhrQ0Xv6Qgab1EtN50G4oDr4/x/+O5pGUG2P5d3isXu+37O3RDw==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -35195,9 +35163,9 @@ "dev": true }, "node_modules/tailwindcss": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz", - "integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -35243,23 +35211,6 @@ "node": ">=10.13.0" } }, - "node_modules/tailwindcss/node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", diff --git a/package.json b/package.json index 6b705b592d..39872eff96 100644 --- a/package.json +++ b/package.json @@ -75,13 +75,13 @@ "@types/node-ipc": "9.2.0", "@types/papaparse": "5.3.14", "@types/proper-lockfile": "4.1.4", - "@types/react": "16.14.54", + "@types/react": "16.14.57", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.4", "@typescript-eslint/eslint-plugin": "6.21.0", "@typescript-eslint/parser": "6.21.0", "@webcomponents/custom-elements": "1.6.0", - "autoprefixer": "10.4.16", + "autoprefixer": "10.4.18", "base64-loader": "1.0.0", "chromatic": "10.9.6", "clean-webpack-plugin": "4.0.0", @@ -89,7 +89,7 @@ "copy-webpack-plugin": "11.0.0", "cross-env": "7.0.3", "css-loader": "6.8.1", - "electron": "28.2.4", + "electron": "28.2.5", "electron-builder": "24.9.1", "electron-log": "5.0.1", "electron-reload": "2.0.0-alpha.1", @@ -134,7 +134,7 @@ "sass-loader": "13.3.2", "storybook": "7.6.17", "style-loader": "3.3.3", - "tailwindcss": "3.3.5", + "tailwindcss": "3.4.1", "ts-jest": "29.1.2", "ts-loader": "9.5.1", "tsconfig-paths-webpack-plugin": "4.1.0", @@ -214,7 +214,7 @@ "replacestream": "4.0.3" }, "resolutions": { - "@types/react": "16.14.54" + "@types/react": "16.14.57" }, "lint-staged": { "*": "prettier --cache --ignore-unknown --write",