Localize key ChatScreen labels and media badges
Some checks failed
Android CI / android (push) Has started running
CI / test (push) Has been cancelled
Android Release / release (push) Has started running

This commit is contained in:
2026-03-11 21:45:48 +03:00
parent e5e4fd653e
commit d649cf1cb4
3 changed files with 78 additions and 20 deletions

View File

@@ -386,7 +386,8 @@ fun ChatScreen(
val senderNameByUserId = remember(state.messages, state.chatMembers) {
val fromMembers = state.chatMembers.associate { member ->
val resolved = member.name.ifBlank {
member.username?.takeIf { it.isNotBlank() }?.let { "@$it" } ?: "User #${member.userId}"
member.username?.takeIf { it.isNotBlank() }?.let { "@$it" }
?: context.getString(R.string.chat_user_fallback_with_id, member.userId)
}
member.userId to resolved
}
@@ -694,7 +695,9 @@ fun ChatScreen(
}
Column(modifier = Modifier.weight(1f)) {
Text(
text = state.chatTitle.ifBlank { "Chat #${state.chatId}" },
text = state.chatTitle.ifBlank {
stringResource(id = R.string.chat_title_fallback, state.chatId)
},
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
@@ -1287,7 +1290,9 @@ fun ChatScreen(
}
Column(modifier = Modifier.weight(1f)) {
Text(
text = state.chatTitle.ifBlank { "Chat #${state.chatId}" },
text = state.chatTitle.ifBlank {
stringResource(id = R.string.chat_title_fallback, state.chatId)
},
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold,
)
@@ -1977,7 +1982,7 @@ fun ChatScreen(
.clickable { emojiPickerTab = tab },
) {
Text(
text = tab.title,
text = stringResource(id = tab.titleRes),
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
style = MaterialTheme.typography.labelLarge,
)
@@ -2170,6 +2175,7 @@ private fun MessageBubble(
onClick: () -> Unit,
onLongPress: () -> Unit,
) {
val context = LocalContext.current
val isOutgoing = message.isOutgoing
val renderAsChannelPost = isChannelChat
val alignAsOutgoing = isOutgoing && !renderAsChannelPost
@@ -2360,7 +2366,11 @@ private fun MessageBubble(
if (isLegacyImageUrlMessage) {
val isLegacyStickerMessage = isStickerAsset(fileUrl = textUrl, fileType = "image/url")
val badgeLabel = if (isLegacyStickerMessage) null else mediaBadgeLabel(fileType = "image/url", url = textUrl)
val badgeLabel = if (isLegacyStickerMessage) {
null
} else {
mediaBadgeLabel(fileType = "image/url", url = textUrl, context = context)
}
val openable = !isLegacyStickerMessage && badgeLabel == null
Box(
modifier = Modifier
@@ -2434,7 +2444,11 @@ private fun MessageBubble(
if (imageAttachments.size == 1) {
val single = imageAttachments.first()
val isStickerImage = isStickerAsset(fileUrl = single.fileUrl, fileType = single.fileType)
val badgeLabel = if (isStickerImage) null else mediaBadgeLabel(fileType = single.fileType, url = single.fileUrl)
val badgeLabel = if (isStickerImage) {
null
} else {
mediaBadgeLabel(fileType = single.fileType, url = single.fileUrl, context = context)
}
val openable = !isStickerImage && badgeLabel == null
Box(
modifier = Modifier
@@ -2475,7 +2489,11 @@ private fun MessageBubble(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
rowItems.forEach { image ->
val badgeLabel = mediaBadgeLabel(fileType = image.fileType, url = image.fileUrl)
val badgeLabel = mediaBadgeLabel(
fileType = image.fileType,
url = image.fileUrl,
context = context,
)
val openable = badgeLabel == null
Box(
modifier = Modifier
@@ -2533,9 +2551,15 @@ private fun MessageBubble(
playbackTitle = message.senderDisplayName?.ifBlank { null }
?: extractFileName(attachment.fileUrl),
playbackSubtitle = if (message.type.contains("voice", ignoreCase = true)) {
"Voice message • ${formatMessageTime(message.createdAt)}"
stringResource(
id = R.string.chat_playback_subtitle_voice,
formatMessageTime(message.createdAt),
)
} else {
"Audio • ${formatMessageTime(message.createdAt)}"
stringResource(
id = R.string.chat_playback_subtitle_audio,
formatMessageTime(message.createdAt),
)
},
messageId = message.id,
onPlaybackChanged = onAudioPlaybackChanged,
@@ -2741,10 +2765,10 @@ private sealed interface ChatTimelineItem {
) : ChatTimelineItem
}
private enum class ComposerPickerTab(val title: String) {
Emoji("Эмодзи"),
Gif("GIF"),
Sticker("Стикеры"),
private enum class ComposerPickerTab(@StringRes val titleRes: Int) {
Emoji(R.string.chat_picker_tab_emoji),
Gif(R.string.chat_picker_tab_gif),
Sticker(R.string.chat_picker_tab_stickers),
}
private data class RemotePickerItem(
@@ -3882,12 +3906,19 @@ private fun ChatMembersTabContent(
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = member.name.ifBlank { member.username?.let { "@$it" } ?: "User #${member.userId}" },
text = member.name.ifBlank {
member.username?.let { "@$it" }
?: stringResource(id = R.string.chat_user_fallback_with_id, member.userId)
},
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.SemiBold,
)
Text(
text = "${member.role} • id ${member.userId}",
text = stringResource(
id = R.string.chat_member_role_with_id,
member.role,
member.userId,
),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -3993,12 +4024,15 @@ private fun ChatMembersTabContent(
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = ban.name.ifBlank { ban.username?.let { "@$it" } ?: "User #${ban.userId}" },
text = ban.name.ifBlank {
ban.username?.let { "@$it" }
?: stringResource(id = R.string.chat_user_fallback_with_id, ban.userId)
},
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.SemiBold,
)
Text(
text = "id ${ban.userId}",
text = stringResource(id = R.string.chat_member_id, ban.userId),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -4115,11 +4149,15 @@ private fun isStickerAsset(fileUrl: String, fileType: String): Boolean {
return isStickerLikeUrl(fileUrl)
}
private fun mediaBadgeLabel(fileType: String, url: String): String? {
private fun mediaBadgeLabel(
fileType: String,
url: String,
context: Context,
): String? {
val normalizedType = fileType.lowercase(Locale.getDefault())
return when {
normalizedType.contains("gif") || isGifLikeUrl(url) -> "GIF"
normalizedType.contains("webp") || isStickerLikeUrl(url) -> "Sticker"
normalizedType.contains("gif") || isGifLikeUrl(url) -> context.getString(R.string.chat_media_badge_gif)
normalizedType.contains("webp") || isStickerLikeUrl(url) -> context.getString(R.string.chat_media_badge_sticker)
else -> null
}
}

View File

@@ -147,6 +147,16 @@
<string name="chat_member_action_kick">Кикнуть</string>
<string name="chat_member_action_unban">Разбанить</string>
<string name="chat_media_badge_video">Видео</string>
<string name="chat_media_badge_gif">GIF</string>
<string name="chat_media_badge_sticker">Стикер</string>
<string name="chat_picker_tab_emoji">Эмодзи</string>
<string name="chat_picker_tab_gif">GIF</string>
<string name="chat_picker_tab_stickers">Стикеры</string>
<string name="chat_playback_subtitle_voice">Голосовое сообщение • %1$s</string>
<string name="chat_playback_subtitle_audio">Аудио • %1$s</string>
<string name="chat_user_fallback_with_id">Пользователь #%1$d</string>
<string name="chat_member_role_with_id">%1$s • id %2$d</string>
<string name="chat_member_id">id %1$d</string>
<string name="chat_member_dialog_demote_title">Понизить админа</string>
<string name="chat_member_dialog_demote_body">Понизить %1$s до участника?</string>
<string name="chat_member_dialog_transfer_title">Передача owner</string>

View File

@@ -147,6 +147,16 @@
<string name="chat_member_action_kick">Kick</string>
<string name="chat_member_action_unban">Unban</string>
<string name="chat_media_badge_video">Video</string>
<string name="chat_media_badge_gif">GIF</string>
<string name="chat_media_badge_sticker">Sticker</string>
<string name="chat_picker_tab_emoji">Emoji</string>
<string name="chat_picker_tab_gif">GIF</string>
<string name="chat_picker_tab_stickers">Stickers</string>
<string name="chat_playback_subtitle_voice">Voice message • %1$s</string>
<string name="chat_playback_subtitle_audio">Audio • %1$s</string>
<string name="chat_user_fallback_with_id">User #%1$d</string>
<string name="chat_member_role_with_id">%1$s • id %2$d</string>
<string name="chat_member_id">id %1$d</string>
<string name="chat_member_dialog_demote_title">Demote admin</string>
<string name="chat_member_dialog_demote_body">Demote %1$s to member?</string>
<string name="chat_member_dialog_transfer_title">Transfer ownership</string>