diff --git a/features/invitepeople/impl/build.gradle.kts b/features/invitepeople/impl/build.gradle.kts index 390ccce7b9..185497c25b 100644 --- a/features/invitepeople/impl/build.gradle.kts +++ b/features/invitepeople/impl/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) implementation(projects.libraries.uiStrings) + implementation(projects.libraries.uiUtils) implementation(projects.libraries.androidutils) implementation(projects.libraries.usersearch.api) implementation(libs.coil.compose) diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt index e8ce5222eb..289ac193d4 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt @@ -55,6 +55,7 @@ import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.getBestName import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.libraries.ui.utils.strings.simplePluralStringResource import kotlinx.collections.immutable.ImmutableList @Composable @@ -263,16 +264,16 @@ private fun InvitePeopleConfirmModal( dragHandle = null, ) { IconTitleSubtitleMolecule( - title = if (users.size > 1) { - stringResource(R.string.screen_invite_users_confirm_dialog_title_mutiple_users) - } else { - stringResource(R.string.screen_invite_users_confirm_dialog_title_one_user) - }, - subTitle = if (users.size > 1) { - stringResource(R.string.screen_invite_users_confirm_dialog_subtitle_multiple_users) - } else { - stringResource(R.string.screen_invite_users_confirm_dialog_subtitle_one_user) - }, + title = simplePluralStringResource( + resIdForOne = R.string.screen_invite_users_confirm_dialog_title_one_user, + resIdForOthers = R.string.screen_invite_users_confirm_dialog_title_mutiple_users, + count = users.size, + ), + subTitle = simplePluralStringResource( + resIdForOne = R.string.screen_invite_users_confirm_dialog_subtitle_one_user, + resIdForOthers = R.string.screen_invite_users_confirm_dialog_subtitle_multiple_users, + count = users.size, + ), iconStyle = BigIcon.Style.Default(CompoundIcons.UserAddSolid()), modifier = Modifier.padding( top = 32.dp, diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt index 3cd004f8a8..07e55410d1 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt @@ -27,4 +27,9 @@ class PreviewStringProvider( override fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String { return resources.getQuantityString(resId, quantity, *formatArgs) } + + override fun getSimpleQuantityString(resIdForOne: Int, resIdForOthers: Int, quantity: Int, vararg formatArgs: Any?): String { + val resId = if (quantity == 1) resIdForOne else resIdForOthers + return resources.getString(resId, *formatArgs) + } } diff --git a/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/strings/Plurals.kt b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/strings/Plurals.kt new file mode 100644 index 0000000000..fe927e725d --- /dev/null +++ b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/strings/Plurals.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.ui.utils.strings + +import androidx.annotation.StringRes +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.res.stringResource + +/** + * Similar to [androidx.compose.ui.res.pluralStringResource] but with separate resource ids for singular and plural values. + * Useful when we want to use different strings for singular and plural forms but not mentioning the actual quantity in the string. + * In this case, we cannot use getQuantityString, because some locales have more than two plural forms, and require the quantity to + * be part of the resulting strings. + * @param resIdForOne Resource id for the case when [count] is 1. + * @param resIdForOthers Resource id for the other cases ([count] is not 1). + * @param count The quantity to determine whether to use singular or plural form. Must be greater than or equal to 1. + * @param formatArgs The format arguments that will be used for substitution in the resulting string. Will be applied to either + * the singular or plural string depending on the quantity. + * @return The localized string corresponding to the given quantity. + */ +@Composable +@ReadOnlyComposable +fun simplePluralStringResource( + @StringRes resIdForOne: Int, + @StringRes resIdForOthers: Int, + count: Int, + vararg formatArgs: Any, +): String { + val resId = if (count == 1) resIdForOne else resIdForOthers + return stringResource(resId, *formatArgs) +} diff --git a/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/strings/StringProvider.kt b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/strings/StringProvider.kt index 9a8ff23eb5..4570e09921 100644 --- a/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/strings/StringProvider.kt +++ b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/strings/StringProvider.kt @@ -34,5 +34,30 @@ interface StringProvider { * stripped of styled text information. */ fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String + + /** + * Returns a localized formatted string from the application's package's + * default string table, substituting the format arguments as defined in + * [java.util.Formatter] and [java.lang.String.format], based on the given quantity. + */ fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String + + /** + * Similar to [getQuantityString] but with separate resource ids for singular and plural values. + * Useful when we want to use different strings for singular and plural forms but not mentioning the actual quantity in the string. + * In this case, we cannot use getQuantityString, because some locales have more than two plural forms, and require the quantity to + * be part of the resulting strings. + * @param resIdForOne Resource id for the case when [quantity] is 1. + * @param resIdForOthers Resource id for the other cases ([quantity] is not 1). + * @param quantity The quantity to determine whether to use singular or plural form. Must be greater than or equal to 1. + * @param formatArgs The format arguments that will be used for substitution in the resulting string. Will be applied to either + * the singular or plural string depending on the quantity. + * @return The localized string corresponding to the given quantity. + */ + fun getSimpleQuantityString( + @StringRes resIdForOne: Int, + @StringRes resIdForOthers: Int, + quantity: Int, + vararg formatArgs: Any?, + ): String } diff --git a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/AndroidStringProvider.kt b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/AndroidStringProvider.kt index b095348c41..b0e14c1db9 100644 --- a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/AndroidStringProvider.kt +++ b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/AndroidStringProvider.kt @@ -28,4 +28,14 @@ class AndroidStringProvider(private val resources: Resources) : StringProvider { override fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String { return resources.getQuantityString(resId, quantity, *formatArgs) } + + override fun getSimpleQuantityString( + resIdForOne: Int, + resIdForOthers: Int, + quantity: Int, + vararg formatArgs: Any?, + ): String { + val resId = if (quantity == 1) resIdForOne else resIdForOthers + return resources.getString(resId, *formatArgs) + } } diff --git a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt index c2867dca3b..0412a8a03a 100644 --- a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt +++ b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt @@ -28,4 +28,14 @@ class FakeStringProvider( lastResIdParam = resId return defaultResult + " ($quantity) " + formatArgs.joinToString() } + + override fun getSimpleQuantityString( + resIdForOne: Int, + resIdForOthers: Int, + quantity: Int, + vararg formatArgs: Any?, + ): String { + lastResIdParam = if (quantity == 1) resIdForOne else resIdForOthers + return defaultResult + " ($quantity) " + formatArgs.joinToString() + } } diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/InstrumentationStringProvider.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/InstrumentationStringProvider.kt index 75d91c4a88..93df350c44 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/InstrumentationStringProvider.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/InstrumentationStringProvider.kt @@ -24,4 +24,9 @@ class InstrumentationStringProvider : StringProvider { override fun getQuantityString(resId: Int, quantity: Int, vararg formatArgs: Any?): String { return resource.getQuantityString(resId, quantity, *formatArgs) } + + override fun getSimpleQuantityString(resIdForOne: Int, resIdForOthers: Int, quantity: Int, vararg formatArgs: Any?): String { + val resId = if (quantity == 1) resIdForOne else resIdForOthers + return resource.getString(resId, *formatArgs) + } }