Skip to content

Commit 75cc14b

Browse files
authored
Improve message list TalkBack accessibility (#6440)
* Make message rows accessible to TalkBack Message rows previously announced as "Message item, …" with the sender name silently hidden from TalkBack and no action labels on the row's clickable, so users heard the generic "double-tap to activate" hint with no verb and could not tell who sent an incoming message in a group or thread. Push the a11y content down to the leaves so Compose's natural merge composes them: - `MessageFooter` sender name dropped its `clearAndSetSemantics { testTag = … }` wrapper, which had been wiping the Text's natural `text` semantic. Replaced with `Modifier.testTag(…)` so the testTag survives and the user name is announced. - `MessageContainer` dropped the static "Message item" placeholder `contentDescription` on the outer Row. `combinedClickable` gains `onClickLabel = "Open thread"` (when `canOpenThread`) and `onLongClickLabel = "Show message options"` (when `canOpenActions`), each gated to the actions that actually fire. Adds the two new strings across all 7 supported locales and drops the now-unused `stream_compose_cd_message_item` placeholder from all 8 locales. * Make message list separators accessible as headings The date and unread separators in the message list are visual landmarks that screen-reader users need to skim a long conversation, but neither exposed a `heading()` semantic, so TalkBack's swipe-by-heading gesture skipped them. The unread separator also rendered "1 unread messages" because its label was a `<string>` with no plural form. Add `Modifier.semantics { heading() }` to both the date separator (`DefaultMessageDateSeparatorContent`) and the unread separator (`DefaultMessageUnreadSeparatorContent`). Convert `stream_compose_message_list_unread_separator` from `<string>` to `<plurals>` across all 8 locales so n=1 reads as a singular ("1 unread message") and n>1 keeps the plural. Update the caller to use `pluralStringResource`. * Announce typing indicator to TalkBack The typing indicator in the message list shows avatars and animated dots but no text, so TalkBack picked nothing up — it neither focused the bubble nor announced when someone started typing. Wrap the indicator's outer Row in a polite live region with a localized description ("X is typing" / "X and Y are typing" / "N people are typing", reusing the existing `stream_compose_channel_list_typing_*` strings since the wording is identical), and merge descendants so the bubble announces as a single unit. The description-building logic is extracted to a small private helper to keep the composable under detekt's method-length cap. * Announce reaction state to TalkBack The reaction toggles in the long-press reactions picker rendered a checked background to indicate the user's own reactions, but exposed nothing to TalkBack — no role, no selected state, just "double-tap to activate". A user could not tell which reactions they had already chosen. In-bubble reactions had a similar gap: each chip's clickable had no `onClickLabel`, so TalkBack announced the generic "double-tap to activate" with no verb. - `ReactionToggle` swaps `Modifier.clickable { onChange(!checked) }` for `Modifier.toggleable(value = checked, role = Role.Switch, onValueChange = onChange)`. TalkBack now announces the emoji plus "switch, on / off, double-tap to toggle". - `MessageReactions` adds `onClickLabel = "Show reactions"` to both the `ClusteredMessageReactions` row clickable and the per-chip `ReactionChip` clickable, so focusing a chip says "<emoji>, double tap to Show reactions". Adds `stream_compose_message_reactions_show` across the 7 supported locales. No public API surface change. * Label message attachment tap action for TalkBack Image, video, file and link attachments in the message list previously announced as "double-tap to activate" — generic, no verb. The `combinedClickable` on each of those attachment containers had no `onClickLabel`, so TalkBack fell back to the platform default. Add `onClickLabel = "Open attachment"` to `MediaAttachmentContent`, `FileAttachmentContent`, and `LinkAttachmentContent`. TalkBack now reads "<type> attachment, double tap to Open attachment" for images and videos, and "<filename>, <size>, double tap to Open attachment" for files (the inner Texts already carry the name and size). Adds `stream_compose_message_attachment_open` across the 7 supported locales. No public API surface change. Poll, location, giphy and audio attachments are left for a follow-up — each has its own composable and a different interaction model. Multi- attachment disambiguation (e.g. "Image 1 of 4") is also a separate follow-up; it needs index/total plumbing through the multi-attachment grid. * Use Role.Checkbox for reaction toggle A reaction is a binary checked / unchecked state with no slider metaphor, so Role.Checkbox communicates the semantics more accurately than Role.Switch. * Use pluralStringResource for typing indicator description `pluralStringResource` is the Compose-idiomatic API for plural string lookups; drops the manual `LocalResources.current.getQuantityString` indirection.
1 parent 677ebe7 commit 75cc14b

16 files changed

Lines changed: 128 additions & 34 deletions

File tree

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/FileAttachmentContent.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import androidx.compose.ui.draw.clip
3737
import androidx.compose.ui.graphics.Color
3838
import androidx.compose.ui.layout.ContentScale
3939
import androidx.compose.ui.platform.testTag
40+
import androidx.compose.ui.res.stringResource
4041
import androidx.compose.ui.text.TextStyle
4142
import androidx.compose.ui.text.style.TextOverflow
4243
import androidx.compose.ui.tooling.preview.Preview
@@ -45,6 +46,7 @@ import io.getstream.chat.android.client.utils.attachment.isAudio
4546
import io.getstream.chat.android.client.utils.attachment.isFile
4647
import io.getstream.chat.android.client.utils.attachment.isImage
4748
import io.getstream.chat.android.client.utils.attachment.isVideo
49+
import io.getstream.chat.android.compose.R
4850
import io.getstream.chat.android.compose.state.messages.attachments.AttachmentState
4951
import io.getstream.chat.android.compose.ui.attachments.preview.handler.AttachmentPreviewHandler
5052
import io.getstream.chat.android.compose.ui.components.LoadingIndicator
@@ -100,6 +102,7 @@ public fun FileAttachmentContent(
100102
)
101103
.testTag("Stream_MultipleFileAttachmentsColumn"),
102104
) {
105+
val openAttachmentLabel = stringResource(R.string.stream_compose_message_attachment_open)
103106
for (attachment in message.attachments) {
104107
if (attachment.isFile() || attachment.isAudio()) {
105108
ChatTheme.componentFactory.FileAttachmentItem(
@@ -114,6 +117,7 @@ public fun FileAttachmentContent(
114117
.combinedClickable(
115118
indication = ripple(),
116119
interactionSource = remember { MutableInteractionSource() },
120+
onClickLabel = openAttachmentLabel,
117121
onClick = { onItemClick(previewHandlers, attachment) },
118122
onLongClick = { attachmentState.onLongItemClick(message) },
119123
),

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/LinkAttachmentContent.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import androidx.compose.ui.layout.ContentScale
5050
import androidx.compose.ui.platform.LocalContext
5151
import androidx.compose.ui.platform.testTag
5252
import androidx.compose.ui.res.painterResource
53+
import androidx.compose.ui.res.stringResource
5354
import androidx.compose.ui.text.style.TextOverflow
5455
import androidx.compose.ui.tooling.preview.Preview
5556
import androidx.compose.ui.unit.dp
@@ -105,6 +106,7 @@ public fun LinkAttachmentContent(
105106
}
106107

107108
val context = LocalContext.current
109+
val openAttachmentLabel = stringResource(R.string.stream_compose_message_attachment_open)
108110
val textColor = MessageStyling.textColor(outgoing = state.isMine)
109111
val baseModifier = modifier
110112
.padding(MessageStyling.messageSectionPadding)
@@ -114,6 +116,7 @@ public fun LinkAttachmentContent(
114116
.combinedClickable(
115117
indication = ripple(),
116118
interactionSource = remember { MutableInteractionSource() },
119+
onClickLabel = openAttachmentLabel,
117120
onClick = {
118121
onItemClick(
119122
LinkAttachmentClickData(

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/content/MediaAttachmentContent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ internal fun MediaAttachmentContentItem(
452452
attachment.uploadState is Attachment.UploadState.InProgress ||
453453
attachment.uploadState is Attachment.UploadState.Idle
454454

455+
val openAttachmentLabel = stringResource(R.string.stream_compose_message_attachment_open)
455456
Box(
456457
modifier = modifier
457458
.semantics {
@@ -465,6 +466,7 @@ internal fun MediaAttachmentContentItem(
465466
.combinedClickable(
466467
interactionSource = remember { MutableInteractionSource() },
467468
indication = ripple(),
469+
onClickLabel = openAttachmentLabel,
468470
onClick = {
469471
if (message.syncStatus == SyncStatus.COMPLETED) {
470472
onItemClick(

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageFooter.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ import androidx.compose.ui.platform.LocalContext
2727
import androidx.compose.ui.platform.testTag
2828
import androidx.compose.ui.res.pluralStringResource
2929
import androidx.compose.ui.res.stringResource
30-
import androidx.compose.ui.semantics.clearAndSetSemantics
31-
import androidx.compose.ui.semantics.testTag
3230
import androidx.compose.ui.text.style.TextOverflow
3331
import io.getstream.chat.android.client.extensions.getCreatedAtOrNull
3432
import io.getstream.chat.android.client.utils.message.isDeleted
@@ -91,9 +89,7 @@ public fun MessageFooter(
9189
if (!messageItem.isMine) {
9290
Text(
9391
modifier = Modifier
94-
.clearAndSetSemantics {
95-
testTag = "Stream_MessageAuthorName"
96-
}
92+
.testTag("Stream_MessageAuthorName")
9793
.padding(end = StreamTokens.spacingXs)
9894
.weight(1f, fill = false),
9995
text = message.user.name,

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageReactions.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ import androidx.compose.ui.Modifier
3434
import androidx.compose.ui.draw.clip
3535
import androidx.compose.ui.platform.testTag
3636
import androidx.compose.ui.res.pluralStringResource
37+
import androidx.compose.ui.res.stringResource
3738
import androidx.compose.ui.semantics.contentDescription
3839
import androidx.compose.ui.semantics.semantics
3940
import androidx.compose.ui.semantics.testTag
4041
import androidx.compose.ui.tooling.preview.Preview
4142
import androidx.compose.ui.unit.dp
43+
import io.getstream.chat.android.compose.R
4244
import io.getstream.chat.android.compose.previewdata.PreviewReactionData
4345
import io.getstream.chat.android.compose.state.messages.MessageReactionItemState
4446
import io.getstream.chat.android.compose.ui.components.reactions.ReactionIconSize
@@ -67,6 +69,7 @@ public fun ClusteredMessageReactions(
6769
reactions.size,
6870
reactions.size,
6971
)
72+
val openLabel = stringResource(R.string.stream_compose_message_reactions_show)
7073
val colors = ChatTheme.colors
7174
val count = reactions.sumOf(MessageReactionItemState::count)
7275

@@ -78,7 +81,7 @@ public fun ClusteredMessageReactions(
7881
}
7982
.background(colors.backgroundCoreElevation1, CircleShape)
8083
.border(1.dp, color = colors.borderCoreSubtle, shape = CircleShape)
81-
.ifNotNull(onClick) { clip(CircleShape).clickable(onClick = it) }
84+
.ifNotNull(onClick) { clip(CircleShape).clickable(onClickLabel = openLabel, onClick = it) }
8285
.padding(horizontal = StreamTokens.spacingXs, vertical = StreamTokens.spacing2xs),
8386
verticalAlignment = Alignment.CenterVertically,
8487
horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacing2xs),
@@ -175,13 +178,14 @@ private fun ReactionChip(
175178
onClick: (() -> Unit)?,
176179
content: @Composable RowScope.() -> Unit,
177180
) {
181+
val openLabel = stringResource(R.string.stream_compose_message_reactions_show)
178182
val colors = ChatTheme.colors
179183
Row(
180184
modifier = Modifier
181185
.fillMaxHeight()
182186
.background(colors.backgroundCoreElevation1, CircleShape)
183187
.border(1.dp, color = colors.borderCoreSubtle, shape = CircleShape)
184-
.ifNotNull(onClick) { clip(CircleShape).clickable(onClick = it) }
188+
.ifNotNull(onClick) { clip(CircleShape).clickable(onClickLabel = openLabel, onClick = it) }
185189
.padding(horizontal = StreamTokens.spacingXs, vertical = StreamTokens.spacing2xs),
186190
verticalAlignment = Alignment.CenterVertically,
187191
horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacing2xs),

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/reactions/ReactionToggle.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,20 @@ import androidx.compose.foundation.layout.Row
2323
import androidx.compose.foundation.layout.defaultMinSize
2424
import androidx.compose.foundation.layout.padding
2525
import androidx.compose.foundation.layout.wrapContentSize
26+
import androidx.compose.foundation.selection.toggleable
2627
import androidx.compose.foundation.shape.CircleShape
2728
import androidx.compose.runtime.Composable
2829
import androidx.compose.ui.Alignment
2930
import androidx.compose.ui.Modifier
3031
import androidx.compose.ui.draw.clip
32+
import androidx.compose.ui.semantics.Role
3133
import androidx.compose.ui.tooling.preview.Preview
3234
import androidx.compose.ui.unit.Dp
3335
import androidx.compose.ui.unit.dp
3436
import io.getstream.chat.android.compose.ui.theme.ChatTheme
3537
import io.getstream.chat.android.compose.ui.theme.ReactionIconParams
3638
import io.getstream.chat.android.compose.ui.util.ReactionResolver
3739
import io.getstream.chat.android.compose.ui.util.applyIf
38-
import io.getstream.chat.android.compose.ui.util.clickable
3940
import io.getstream.chat.android.compose.ui.util.ifNotNull
4041

4142
/**
@@ -69,7 +70,11 @@ internal fun ReactionToggle(
6970
background(ChatTheme.colors.backgroundUtilitySelected, CircleShape)
7071
}
7172
.ifNotNull(onCheckedChange) { onChange ->
72-
clip(CircleShape).clickable { onChange(!checked) }
73+
clip(CircleShape).toggleable(
74+
value = checked,
75+
role = Role.Checkbox,
76+
onValueChange = onChange,
77+
)
7378
}
7479
.defaultMinSize(minWidth = containerSize, minHeight = containerSize)
7580
.wrapContentSize(),

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageContainer.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ import androidx.compose.ui.platform.LocalLayoutDirection
6262
import androidx.compose.ui.platform.LocalView
6363
import androidx.compose.ui.platform.testTag
6464
import androidx.compose.ui.res.stringResource
65-
import androidx.compose.ui.semantics.contentDescription
66-
import androidx.compose.ui.semantics.semantics
6765
import androidx.compose.ui.unit.Dp
6866
import androidx.compose.ui.unit.IntOffset
6967
import androidx.compose.ui.unit.LayoutDirection
@@ -177,12 +175,16 @@ public fun MessageContainer(
177175
val canOpenActions = !message.isDeleted() && !message.isUploading()
178176
val onLongItemClick = rememberHapticLongClick(onLongItemClick)
179177

178+
val openThreadLabel = stringResource(R.string.stream_compose_message_item_open_thread)
179+
val messageOptionsLabel = stringResource(R.string.stream_compose_message_item_options)
180180
val clickModifier = Modifier.combinedClickable(
181181
interactionSource = remember(::MutableInteractionSource),
182182
indication = null,
183183
enabled = canOpenThread || canOpenActions,
184184
onClick = { if (canOpenThread) onThreadClick(message) },
185185
onLongClick = { if (canOpenActions) onLongItemClick(message) },
186+
onClickLabel = openThreadLabel.takeIf { canOpenThread },
187+
onLongClickLabel = messageOptionsLabel.takeIf { canOpenActions },
186188
)
187189

188190
val highlightColor = ChatTheme.colors.backgroundCoreHighlight
@@ -198,7 +200,6 @@ public fun MessageContainer(
198200
)
199201

200202
val messageAlignment = ChatTheme.messageAlignmentProvider.provideMessageAlignment(messageItem)
201-
val description = stringResource(id = R.string.stream_compose_cd_message_item)
202203
val optionsVisibility = ChatTheme.config.messageActions.optionsVisibility
203204
val isSwipeable = remember(message, messageItem.ownCapabilities, optionsVisibility) {
204205
optionsVisibility.canReplyToMessage(message, messageItem.ownCapabilities)
@@ -223,8 +224,7 @@ public fun MessageContainer(
223224
.fillMaxWidth()
224225
.wrapContentHeight()
225226
.background(color = color)
226-
.then(clickModifier)
227-
.semantics { contentDescription = description },
227+
.then(clickModifier),
228228
horizontalArrangement = if (messageAlignment == MessageAlignment.Start) {
229229
Arrangement.Start
230230
} else {

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageItem.kt

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ import androidx.compose.ui.layout.positionInWindow
4242
import androidx.compose.ui.platform.testTag
4343
import androidx.compose.ui.res.pluralStringResource
4444
import androidx.compose.ui.res.stringResource
45+
import androidx.compose.ui.semantics.LiveRegionMode
46+
import androidx.compose.ui.semantics.contentDescription
47+
import androidx.compose.ui.semantics.heading
48+
import androidx.compose.ui.semantics.liveRegion
4549
import androidx.compose.ui.semantics.semantics
4650
import androidx.compose.ui.semantics.testTag
4751
import androidx.compose.ui.text.style.TextAlign
@@ -220,6 +224,7 @@ internal fun DefaultMessageDateSeparatorContent(dateSeparator: DateSeparatorItem
220224
modifier = Modifier
221225
.semantics(mergeDescendants = true) {
222226
testTag = "Stream_MessageDateSeparator"
227+
heading()
223228
}
224229
.padding(vertical = StreamTokens.spacingXs)
225230
.fillMaxWidth(),
@@ -233,13 +238,16 @@ internal fun DefaultMessageDateSeparatorContent(dateSeparator: DateSeparatorItem
233238
*/
234239
@Composable
235240
internal fun DefaultMessageUnreadSeparatorContent(unreadSeparatorItemState: UnreadSeparatorItemState) {
241+
val unreadCount = unreadSeparatorItemState.unreadCount
236242
MessagesStripDivider(
237243
modifier = Modifier.semantics(mergeDescendants = true) {
238244
testTag = "Stream_UnreadMessagesBadge"
245+
heading()
239246
},
240-
text = stringResource(
241-
R.string.stream_compose_message_list_unread_separator,
242-
unreadSeparatorItemState.unreadCount,
247+
text = pluralStringResource(
248+
R.plurals.stream_compose_message_list_unread_separator,
249+
unreadCount,
250+
unreadCount,
243251
),
244252
)
245253
}
@@ -326,10 +334,15 @@ internal fun DefaultMessageModeratedContent(moderatedMessageItemState: Moderated
326334
*/
327335
@Composable
328336
internal fun DefaultMessageTypingIndicatorContent(state: TypingItemState) {
337+
val typingDescription = typingUsersDescription(state.typingUsers)
329338
Row(
330339
modifier = Modifier
331340
.padding(StreamTokens.spacingXs)
332-
.testTag("Stream_MessageListTypingIndicator"),
341+
.testTag("Stream_MessageListTypingIndicator")
342+
.semantics(mergeDescendants = true) {
343+
contentDescription = typingDescription
344+
liveRegion = LiveRegionMode.Polite
345+
},
333346
horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacingXs),
334347
verticalAlignment = Alignment.Bottom,
335348
) {
@@ -364,6 +377,25 @@ internal fun DefaultMessageTypingIndicatorContent(state: TypingItemState) {
364377
}
365378
}
366379

380+
@Composable
381+
private fun typingUsersDescription(typingUsers: List<User>): String =
382+
when (typingUsers.size) {
383+
1 -> stringResource(
384+
R.string.stream_compose_channel_list_typing_one,
385+
typingUsers.first().name,
386+
)
387+
2 -> stringResource(
388+
R.string.stream_compose_channel_list_typing_two,
389+
typingUsers[0].name,
390+
typingUsers[1].name,
391+
)
392+
else -> pluralStringResource(
393+
R.plurals.stream_compose_channel_list_typing_many,
394+
typingUsers.size,
395+
typingUsers.size,
396+
)
397+
}
398+
367399
private const val MaxTypingUsersAvatars = 3
368400

369401
@Preview(showBackground = true)

stream-chat-android-compose/src/main/res/values-es/strings.xml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
<string name="stream_compose_channel_item_muted">"silenciado"</string>
6060
<string name="stream_compose_channel_item_open">"Abrir conversación"</string>
6161
<string name="stream_compose_channel_item_options">"Abrir opciones de conversación"</string>
62-
<string name="stream_compose_cd_message_item">"Elemento de mensaje"</string>
6362
<string name="stream_compose_cd_play_button">"Botón de reproducción"</string>
6463
<string name="stream_compose_channel_list_draft">"Borrador: "</string>
6564
<string name="stream_compose_channel_list_empty_channels">"No hay conversaciones aún"</string>
@@ -123,6 +122,10 @@
123122
<string name="stream_compose_message_composer_attachments_expanded">"expandido"</string>
124123
<string name="stream_compose_message_deleted">"Mensaje eliminado"</string>
125124
<string name="stream_compose_message_deleted_preview">"Mensaje eliminado"</string>
125+
<string name="stream_compose_message_item_open_thread">"Abrir hilo"</string>
126+
<string name="stream_compose_message_item_options">"Mostrar opciones del mensaje"</string>
127+
<string name="stream_compose_message_attachment_open">"Abrir archivo adjunto"</string>
128+
<string name="stream_compose_message_reactions_show">"Mostrar reacciones"</string>
126129
<string name="stream_compose_message_list_auto_translated">"Traducido"</string>
127130
<string name="stream_compose_message_list_empty_messages">"Sin mensajes"</string>
128131
<string name="stream_compose_message_list_error_cannot_open_link">"No hay ninguna aplicación para ver esta URL:\n%s"</string>
@@ -135,7 +138,10 @@
135138
<string name="stream_compose_message_list_show_translation">"Mostrar traducción"</string>
136139
<string name="stream_compose_message_list_thread_footnote_thread_replies">"%d respuestas"</string>
137140
<string name="stream_compose_message_list_thread_footnote_thread_reply">"%d respuesta"</string>
138-
<string name="stream_compose_message_list_unread_separator">"%d mensajes no leídos"</string>
141+
<plurals name="stream_compose_message_list_unread_separator">
142+
<item quantity="one">"%d mensaje no leído"</item>
143+
<item quantity="other">"%d mensajes no leídos"</item>
144+
</plurals>
139145
<string name="stream_compose_message_list_unsupported_attachment">"Archivo adjunto no compatible"</string>
140146
<string name="stream_compose_message_list_view">"Ver"</string>
141147
<string name="stream_compose_message_list_view_original">"Ver original"</string>

stream-chat-android-compose/src/main/res/values-fr/strings.xml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
<string name="stream_compose_channel_item_muted">"en sourdine"</string>
6060
<string name="stream_compose_channel_item_open">"Ouvrir la conversation"</string>
6161
<string name="stream_compose_channel_item_options">"Ouvrir les options de conversation"</string>
62-
<string name="stream_compose_cd_message_item">"Élément de message"</string>
6362
<string name="stream_compose_cd_play_button">"Bouton de lecture"</string>
6463
<string name="stream_compose_channel_list_draft">"Brouillon : "</string>
6564
<string name="stream_compose_channel_list_empty_channels">"Aucune conversation pour le moment"</string>
@@ -123,6 +122,10 @@
123122
<string name="stream_compose_message_composer_attachments_expanded">"développé"</string>
124123
<string name="stream_compose_message_deleted">"Message supprimé"</string>
125124
<string name="stream_compose_message_deleted_preview">"Message supprimé"</string>
125+
<string name="stream_compose_message_item_open_thread">"Ouvrir le fil"</string>
126+
<string name="stream_compose_message_item_options">"Afficher les options du message"</string>
127+
<string name="stream_compose_message_attachment_open">"Ouvrir la pièce jointe"</string>
128+
<string name="stream_compose_message_reactions_show">"Afficher les réactions"</string>
126129
<string name="stream_compose_message_list_auto_translated">"Traduit"</string>
127130
<string name="stream_compose_message_list_empty_messages">"Aucun message"</string>
128131
<string name="stream_compose_message_list_error_cannot_open_link">"Aucune application pour ouvrir cette URL :\n%s"</string>
@@ -135,7 +138,10 @@
135138
<string name="stream_compose_message_list_show_translation">"Afficher la traduction"</string>
136139
<string name="stream_compose_message_list_thread_footnote_thread_replies">"%d réponses"</string>
137140
<string name="stream_compose_message_list_thread_footnote_thread_reply">"%d réponse"</string>
138-
<string name="stream_compose_message_list_unread_separator">"%d messages non lus"</string>
141+
<plurals name="stream_compose_message_list_unread_separator">
142+
<item quantity="one">"%d message non lu"</item>
143+
<item quantity="other">"%d messages non lus"</item>
144+
</plurals>
139145
<string name="stream_compose_message_list_unsupported_attachment">"Pièce jointe non prise en charge"</string>
140146
<string name="stream_compose_message_list_view">"Voir"</string>
141147
<string name="stream_compose_message_list_view_original">"Voir l\'original"</string>

0 commit comments

Comments
 (0)