diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModel.kt
index e0b90ec8c8..e2416d5c79 100644
--- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModel.kt
+++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModel.kt
@@ -5,8 +5,10 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.ui.platform.base.util.isValidEmail
+import com.bitwarden.ui.platform.resource.BitwardenPlurals
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.util.Text
+import com.bitwarden.ui.util.asPluralsText
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
@@ -321,8 +323,11 @@ class CompleteRegistrationViewModel @Inject constructor(
it.copy(
dialog = CompleteRegistrationDialog.Error(
title = BitwardenString.an_error_has_occurred.asText(),
- message = BitwardenString.master_password_length_val_message_x
- .asText(MIN_PASSWORD_LENGTH),
+ message = BitwardenPlurals.master_password_length_val_message_x
+ .asPluralsText(
+ quantity = MIN_PASSWORD_LENGTH,
+ args = arrayOf(MIN_PASSWORD_LENGTH),
+ ),
),
)
}
diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/completeregistration/PasswordStrengthIndicator.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/completeregistration/PasswordStrengthIndicator.kt
index 015a64fc43..290382a716 100644
--- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/completeregistration/PasswordStrengthIndicator.kt
+++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/completeregistration/PasswordStrengthIndicator.kt
@@ -24,11 +24,12 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.bitwarden.ui.platform.resource.BitwardenDrawable
+import com.bitwarden.ui.platform.resource.BitwardenPlurals
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.bitwarden.ui.util.asText
@@ -154,7 +155,11 @@ private fun MinimumCharacterCount(
}
Spacer(modifier = Modifier.width(2.dp))
Text(
- text = stringResource(BitwardenString.minimum_characters, minimumCharacterCount),
+ text = pluralStringResource(
+ id = BitwardenPlurals.minimum_characters,
+ count = minimumCharacterCount,
+ formatArgs = arrayOf(minimumCharacterCount),
+ ),
color = BitwardenTheme.colorScheme.text.secondary,
style = BitwardenTheme.typography.labelSmall,
)
diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/resetpassword/ResetPasswordViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/resetpassword/ResetPasswordViewModel.kt
index e64bea4cfa..a28c5d623e 100644
--- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/resetpassword/ResetPasswordViewModel.kt
+++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/resetpassword/ResetPasswordViewModel.kt
@@ -5,8 +5,10 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.ui.platform.base.util.orNullIfBlank
+import com.bitwarden.ui.platform.resource.BitwardenPlurals
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.util.Text
+import com.bitwarden.ui.util.asPluralsText
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
@@ -199,8 +201,11 @@ class ResetPasswordViewModel @Inject constructor(
it.copy(
dialogState = ResetPasswordState.DialogState.Error(
title = BitwardenString.an_error_has_occurred.asText(),
- message = BitwardenString.master_password_length_val_message_x
- .asText(MIN_PASSWORD_LENGTH),
+ message = BitwardenPlurals.master_password_length_val_message_x
+ .asPluralsText(
+ quantity = MIN_PASSWORD_LENGTH,
+ args = arrayOf(MIN_PASSWORD_LENGTH),
+ ),
),
)
}
diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordViewModel.kt
index 184052b7e0..eaaa19c088 100644
--- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordViewModel.kt
+++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordViewModel.kt
@@ -4,8 +4,10 @@ import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.ui.platform.base.BaseViewModel
+import com.bitwarden.ui.platform.resource.BitwardenPlurals
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.util.Text
+import com.bitwarden.ui.util.asPluralsText
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason
@@ -111,8 +113,11 @@ class SetPasswordViewModel @Inject constructor(
it.copy(
dialogState = SetPasswordState.DialogState.Error(
title = BitwardenString.an_error_has_occurred.asText(),
- message = BitwardenString.master_password_length_val_message_x
- .asText(MIN_PASSWORD_LENGTH),
+ message = BitwardenPlurals.master_password_length_val_message_x
+ .asPluralsText(
+ quantity = MIN_PASSWORD_LENGTH,
+ args = arrayOf(MIN_PASSWORD_LENGTH),
+ ),
),
)
}
diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt
index f3725a9392..ac8042231d 100644
--- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt
+++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt
@@ -14,8 +14,10 @@ import com.bitwarden.network.model.PolicyTypeJson
import com.bitwarden.ui.platform.base.BackgroundEvent
import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
+import com.bitwarden.ui.platform.resource.BitwardenPlurals
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.util.Text
+import com.bitwarden.ui.util.asPluralsText
import com.bitwarden.ui.util.asText
import com.bitwarden.vault.CipherView
import com.bitwarden.vault.DecryptCipherListResult
@@ -1945,7 +1947,11 @@ class VaultAddEditViewModel @Inject constructor(
is BreachCountResult.Success -> {
VaultAddEditState.DialogState.Generic(
message = if (result.breachCount > 0) {
- BitwardenString.password_exposed.asText(result.breachCount)
+ BitwardenPlurals.password_exposed
+ .asPluralsText(
+ quantity = result.breachCount,
+ args = arrayOf(result.breachCount),
+ )
} else {
BitwardenString.password_safe.asText()
},
diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt
index dcc15d5a69..088228e1ab 100644
--- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt
+++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt
@@ -13,8 +13,10 @@ import com.bitwarden.ui.platform.base.BackgroundEvent
import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.ui.platform.components.icon.model.IconData
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
+import com.bitwarden.ui.platform.resource.BitwardenPlurals
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.util.Text
+import com.bitwarden.ui.util.asPluralsText
import com.bitwarden.ui.util.asText
import com.bitwarden.ui.util.concat
import com.bitwarden.vault.CipherView
@@ -978,7 +980,10 @@ class VaultItemViewModel @Inject constructor(
is BreachCountResult.Success -> {
VaultItemState.DialogState.Generic(
message = if (result.breachCount > 0) {
- BitwardenString.password_exposed.asText(result.breachCount)
+ BitwardenPlurals.password_exposed.asPluralsText(
+ quantity = result.breachCount,
+ args = arrayOf(result.breachCount),
+ )
} else {
BitwardenString.password_safe.asText()
},
diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModelTest.kt
index 9b116acaf5..492577c821 100644
--- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModelTest.kt
+++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModelTest.kt
@@ -5,7 +5,9 @@ import app.cash.turbine.test
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.data.datasource.disk.base.FakeDispatcherManager
import com.bitwarden.ui.platform.base.BaseViewModelTest
+import com.bitwarden.ui.platform.resource.BitwardenPlurals
import com.bitwarden.ui.platform.resource.BitwardenString
+import com.bitwarden.ui.util.asPluralsText
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_0
@@ -590,7 +592,10 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
passwordInput = input,
dialog = CompleteRegistrationDialog.Error(
title = BitwardenString.an_error_has_occurred.asText(),
- message = BitwardenString.master_password_length_val_message_x.asText(12),
+ message = BitwardenPlurals.master_password_length_val_message_x.asPluralsText(
+ quantity = 12,
+ args = arrayOf(12),
+ ),
),
)
viewModel.trySendAction(CompleteRegistrationAction.CallToActionClick)
diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/resetPassword/ResetPasswordViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/resetPassword/ResetPasswordViewModelTest.kt
index 44c6706e00..c7b9d77853 100644
--- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/resetPassword/ResetPasswordViewModelTest.kt
+++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/resetPassword/ResetPasswordViewModelTest.kt
@@ -3,7 +3,9 @@ package com.x8bit.bitwarden.ui.auth.feature.resetPassword
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.bitwarden.ui.platform.base.BaseViewModelTest
+import com.bitwarden.ui.platform.resource.BitwardenPlurals
import com.bitwarden.ui.platform.resource.BitwardenString
+import com.bitwarden.ui.util.asPluralsText
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
@@ -133,8 +135,10 @@ class ResetPasswordViewModelTest : BaseViewModelTest() {
resetReason = ForcePasswordResetReason.ADMIN_FORCE_PASSWORD_RESET,
dialogState = ResetPasswordState.DialogState.Error(
title = BitwardenString.an_error_has_occurred.asText(),
- message = BitwardenString.master_password_length_val_message_x
- .asText(MIN_PASSWORD_LENGTH),
+ message = BitwardenPlurals.master_password_length_val_message_x.asPluralsText(
+ quantity = MIN_PASSWORD_LENGTH,
+ args = arrayOf(MIN_PASSWORD_LENGTH),
+ ),
),
passwordInput = password,
passwordStrengthState = PasswordStrengthState.WEAK_1,
diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordViewModelTest.kt
index afe54beb1d..70fa8adb94 100644
--- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordViewModelTest.kt
+++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/setpassword/SetPasswordViewModelTest.kt
@@ -3,7 +3,9 @@ package com.x8bit.bitwarden.ui.auth.feature.setpassword
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.bitwarden.ui.platform.base.BaseViewModelTest
+import com.bitwarden.ui.platform.resource.BitwardenPlurals
import com.bitwarden.ui.platform.resource.BitwardenString
+import com.bitwarden.ui.util.asPluralsText
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason
@@ -87,8 +89,10 @@ class SetPasswordViewModelTest : BaseViewModelTest() {
DEFAULT_STATE.copy(
dialogState = SetPasswordState.DialogState.Error(
title = BitwardenString.an_error_has_occurred.asText(),
- message = BitwardenString.master_password_length_val_message_x
- .asText(MIN_PASSWORD_LENGTH),
+ message = BitwardenPlurals.master_password_length_val_message_x.asPluralsText(
+ quantity = MIN_PASSWORD_LENGTH,
+ args = arrayOf(MIN_PASSWORD_LENGTH),
+ ),
),
passwordInput = password,
retypePasswordInput = password,
diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt
index 8faf2b96d1..b22baa5fef 100644
--- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt
+++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt
@@ -20,8 +20,10 @@ import com.bitwarden.network.model.SyncResponseJson
import com.bitwarden.send.SendView
import com.bitwarden.ui.platform.base.BaseViewModelTest
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
+import com.bitwarden.ui.platform.resource.BitwardenPlurals
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.util.Text
+import com.bitwarden.ui.util.asPluralsText
import com.bitwarden.ui.util.asText
import com.bitwarden.vault.CipherListView
import com.bitwarden.vault.CipherView
@@ -2439,7 +2441,10 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
assertEquals(
loginState.copy(
dialog = VaultAddEditState.DialogState.Generic(
- message = BitwardenString.password_exposed.asText(breachCount),
+ message = BitwardenPlurals.password_exposed.asPluralsText(
+ quantity = breachCount,
+ args = arrayOf(breachCount),
+ ),
),
),
awaitItem(),
diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt
index 133c981d57..a29305dc57 100644
--- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt
+++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt
@@ -13,8 +13,10 @@ import com.bitwarden.ui.platform.base.BaseViewModelTest
import com.bitwarden.ui.platform.components.icon.model.IconData
import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData
import com.bitwarden.ui.platform.resource.BitwardenDrawable
+import com.bitwarden.ui.platform.resource.BitwardenPlurals
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.util.Text
+import com.bitwarden.ui.util.asPluralsText
import com.bitwarden.ui.util.asText
import com.bitwarden.ui.util.concat
import com.bitwarden.vault.CipherView
@@ -1289,7 +1291,10 @@ class VaultItemViewModelTest : BaseViewModelTest() {
assertEquals(
loginState.copy(
dialog = VaultItemState.DialogState.Generic(
- message = BitwardenString.password_exposed.asText(breachCount),
+ message = BitwardenPlurals.password_exposed.asPluralsText(
+ quantity = breachCount,
+ args = arrayOf(breachCount),
+ ),
),
),
awaitItem(),
diff --git a/ui/src/main/kotlin/com/bitwarden/ui/util/Text.kt b/ui/src/main/kotlin/com/bitwarden/ui/util/Text.kt
index 7f7d7d5c5f..72ee30017c 100644
--- a/ui/src/main/kotlin/com/bitwarden/ui/util/Text.kt
+++ b/ui/src/main/kotlin/com/bitwarden/ui/util/Text.kt
@@ -115,3 +115,11 @@ fun @receiver:StringRes Int.asText(): Text = ResText(this)
* Convert a resource Id to [Text] with format args.
*/
fun @receiver:StringRes Int.asText(vararg args: Any): Text = ResArgsText(this, args.asList())
+
+/**
+ * Convert a resource Id to [Text] with quantity and format args.
+ */
+fun @receiver:PluralsRes Int.asPluralsText(
+ quantity: Int,
+ vararg args: Any,
+): Text = PluralsText(id = this, quantity = quantity, args = args.asList())
diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml
index 2f5cdf5dc7..bed94b9fe7 100644
--- a/ui/src/main/res/values/strings.xml
+++ b/ui/src/main/res/values/strings.xml
@@ -110,7 +110,10 @@
The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it.
Master password hint (optional)
A master password hint can help you remember your password if you forget it.
- Master password must be at least %1$s characters long.
+
+ - Master password must be at least %d character long.
+ - Master password must be at least %d characters long.
+
Minimum numbers
Minimum special
Never
@@ -309,7 +312,10 @@ Scanning will happen automatically.
Personal Details
Contact Info
Check password for data breaches
- This password has been exposed %1$s time(s) in data breaches. You should change it.
+
+ - This password has been exposed %d time in data breaches. You should change it.
+ - This password has been exposed %d times in data breaches. You should change it.
+
This password was not found in any known data breaches. It should be safe to use.
Identity name
Value
@@ -749,7 +755,10 @@ Do you want to switch to this account?
Bitwarden cannot reset a lost or forgotten master password.
Choose your master password
Choose a unique and strong password to keep your information safe.
- %1$s characters
+
+ - %d character
+ - %d characters
+
Expired link
Please restart registration or try logging in. You may already have an account.
Restart registration