refactor: make the 40 interpolated UI strings format strings, and assert the argument order

Phase 4, third step, of docs/material-design-conformance.md. `Text("Add chapter to
${uiState.artifact.name}")` becomes a resource holding `Add chapter to %1$s` and a call
passing the expression. 49 call sites. Literals in composables go 76 -> 39;
`stringResource` goes 374 -> 424.

**A silent bug in the previous commit's extractor, found by this one.** Imports were
tested with `statement in source`, and the generated accessors are named after their
strings -- so `import mantra.composeapp.generated.resources.translate` is a *prefix* of
`...resources.translate_into_which_dialect`. The substring test decided the import was
already there, and the compiler reported "Unresolved reference 'translate'" in a file
whose imports looked complete. Both extractors now match whole lines, and the helper
carries the explanation.

**Four filters, each earned by something the dry run got wrong.**

*A template that is only interpolation has nothing to translate.* `Text("$name")` would
have become a resource holding `%1$s` -- longer, slower, and no more localisable than the
code it replaced.

*A leading or trailing space means it is being glued to a neighbour.* " \\u00b7 %1$s" is a
separator. The test has to be on the format string rather than on the literal halves: a
template opening with an interpolation leaves the first part empty and the second starting
with the separating space, which makes "%1$s Key packages" look like a fragment when it is
a whole label.

*`\\uXXXX` and `\\"` are Kotlin syntax, not XML.* Left alone they would have shipped as the
six visible characters of the escape. They are decoded into the resource, which is UTF-8
and can hold `·` directly. `\\n` is **not** decoded, because
StringCatalogueJvmTest shows Compose Resources processes that one and a real newline in an
XML value would be reflowed by the parser.

*A term of a `+` concatenation is still not a string.* Same rule as the plain extractor.

**Three copy problems surfaced only here, because interpolated strings had never been
checked.** `m3-title-case.py` excludes anything containing `$` -- an interpolation is not a
literal -- so `"$count Key Packages"` had been invisible to every pass so far, as had
`"replying To ${…}"`. And a third instance of the old product name, in
`"...once they're on Torch."`. All three fixed. Worth noting as a gap in the checker rather
than a one-off: title case inside a template is still unchecked, and there are 83
concatenation fragments left where it could hide.

**Two new assertions, on the two things a compiler cannot see.** Argument *order* is
decided by where each `${…}` sat, and a transposition compiles and reads plausibly --
"Recovered 3 of 12" against "Recovered 12 of 3" -- so a two-argument and a three-argument
string are asserted end to end. The three-argument one doubles as the check that `·`
was decoded rather than passed through.

**What is deliberately left.** 83 literals that are terms of a `+` concatenation.
Reassembling `"a " + x + " b"` into one format string means deciding what the whole
sentence is, and several are pluralisations -- `(if (n == 2) "event" else "events")` --
which want a real plural resource rather than a format argument, and that is an API choice
rather than a rewrite. `m3-extract-formatted.py --remaining` lists them.

**Tests.** 949 pass, 600 jvm over 73 classes and 349 android over 44, up from 947/598/349.
`:composeApp:compileDebugKotlinAndroid` builds; the debug apk installs and runs on
emulator-5554 through onboarding, the message list and a chat room with its text intact.
`m3-audit.sh --check` exits 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kgothatso Ngako
2026-09-08 01:45:35 +02:00
parent 419504c982
commit 2117e22d48
26 changed files with 462 additions and 76 deletions

View File

@@ -63,6 +63,7 @@ import mantra.composeapp.generated.resources.original_text_markdown
import mantra.composeapp.generated.resources.paste_the_chapter_s_markdown_blank_lines
import mantra.composeapp.generated.resources.propose_chapter
import mantra.composeapp.generated.resources.this_artifact_has_no_version_for_a_chapter
import mantra.composeapp.generated.resources.add_chapter_to
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -138,7 +139,7 @@ fun AddChapterScreen(
modifier = Modifier.imePadding(),
topBar = {
TopAppBar(
title = { Text("Add chapter to ${addChapterUIState.artifact.name}") },
title = { Text(stringResource(Res.string.add_chapter_to, addChapterUIState.artifact.name)) },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(

View File

@@ -43,6 +43,9 @@ import org.jetbrains.compose.resources.stringResource
import mantra.composeapp.generated.resources.direct_message_detail
import mantra.composeapp.generated.resources.direct_message_functionality_will_be_here
import mantra.composeapp.generated.resources.invite
import mantra.composeapp.generated.resources.invite_2
import mantra.composeapp.generated.resources.once_invited_will_be_able_to_receive_and
import mantra.composeapp.generated.resources.to_join_the_chat_room
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
@Composable
@@ -119,7 +122,7 @@ fun AddMemberToChatRoomConfirmationScreen(
contentDescription = "Invite new member"
)
Text("Invite ${addMemberToChatRoomConfirmationUIState.profile.humanReadableNameOrPubkey()}")
Text(stringResource(Res.string.invite_2, addMemberToChatRoomConfirmationUIState.profile.humanReadableNameOrPubkey()))
}
},
actions = {
@@ -147,11 +150,11 @@ fun AddMemberToChatRoomConfirmationScreen(
)
Text(
text = "to join the ${addMemberToChatRoomConfirmationUIState.localChatRoom.chatRoom.subject ?: ""} chat room."
text = stringResource(Res.string.to_join_the_chat_room, addMemberToChatRoomConfirmationUIState.localChatRoom.chatRoom.subject ?: "")
)
Text(
text = "Once invited will be able to receive and send private message sent to all the ${addMemberToChatRoomConfirmationUIState.localChatRoom.localParticipants.size} members in the chat room.",
text = stringResource(Res.string.once_invited_will_be_able_to_receive_and, addMemberToChatRoomConfirmationUIState.localChatRoom.localParticipants.size),
textAlign = TextAlign.Center
)

View File

@@ -64,6 +64,7 @@ import mantra.composeapp.generated.resources.add_translation
import mantra.composeapp.generated.resources.propose_translation
import mantra.composeapp.generated.resources.this_artifact_has_no_version_for_a
import mantra.composeapp.generated.resources.translate_into_which_dialect
import mantra.composeapp.generated.resources.translate
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
@@ -137,7 +138,7 @@ fun AddTranslationArtifactVersionScreen(
Scaffold(
topBar = {
TopAppBar(
title = { Text("Translate ${addTranslationUIState.artifact.name}") },
title = { Text(stringResource(Res.string.translate, addTranslationUIState.artifact.name)) },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(

View File

@@ -62,6 +62,8 @@ import mantra.composeapp.generated.resources.no_versions
import mantra.composeapp.generated.resources.translations
import mantra.composeapp.generated.resources.version
import mantra.composeapp.generated.resources.versions
import mantra.composeapp.generated.resources.chapter
import mantra.composeapp.generated.resources.words_characters
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -151,10 +153,10 @@ fun ArtifactDetailScreen(
contentDescription = "Chapter"
)
},
overlineContent = { Text("Chapter ${chapter.index}") },
overlineContent = { Text(stringResource(Res.string.chapter, chapter.index)) },
headlineContent = { Text(chapter.name) },
supportingContent = {
Text("${chapter.wordCount} words · ${chapter.characterCount} characters")
Text(stringResource(Res.string.words_characters, chapter.wordCount, chapter.characterCount))
}
)
}

View File

@@ -44,6 +44,10 @@ import org.jetbrains.compose.resources.stringResource
import mantra.composeapp.generated.resources.chapter_detail
import mantra.composeapp.generated.resources.no_chunks
import mantra.composeapp.generated.resources.original_text
import mantra.composeapp.generated.resources.chapter_words_characters
import mantra.composeapp.generated.resources.chunk
import mantra.composeapp.generated.resources.chunks
import mantra.composeapp.generated.resources.words_characters
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -101,7 +105,7 @@ fun ChapterDetailScreen(
) {
item {
Text(
text = "Chapter ${chapter.index} · ${chapter.wordCount} words · ${chapter.characterCount} characters",
text = stringResource(Res.string.chapter_words_characters, chapter.index, chapter.wordCount, chapter.characterCount),
style = MaterialTheme.typography.labelMedium
)
}
@@ -128,7 +132,7 @@ fun ChapterDetailScreen(
// Chunks
item {
Text(
text = "Chunks (${chapterDetailUIState.chunks.size})",
text = stringResource(Res.string.chunks, chapterDetailUIState.chunks.size),
style = MaterialTheme.typography.labelMedium
)
}
@@ -147,10 +151,10 @@ fun ChapterDetailScreen(
contentDescription = "Chunk"
)
},
overlineContent = { Text("Chunk ${chunk.index}") },
overlineContent = { Text(stringResource(Res.string.chunk, chunk.index)) },
headlineContent = { Text(chunk.text) },
supportingContent = {
Text("${chunk.wordCount} words · ${chunk.characterCount} characters")
Text(stringResource(Res.string.words_characters, chunk.wordCount, chunk.characterCount))
}
)
}

View File

@@ -89,6 +89,9 @@ import mantra.composeapp.generated.resources.projects
import mantra.composeapp.generated.resources.proposals
import mantra.composeapp.generated.resources.reindex_events
import mantra.composeapp.generated.resources.shared_key
import mantra.composeapp.generated.resources.event_s_still_unreadable
import mantra.composeapp.generated.resources.recovered_of_event_s
import mantra.composeapp.generated.resources.recovered_of_still_unreadable
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
@Composable
@@ -706,10 +709,10 @@ private fun ReindexMarmotGroupEventsButton(
"Nothing to reindex · ${report.stored - report.predatingMembership} " +
"event(s) all read$beforeJoining"
report.recovered > 0 && report.failed > 0 ->
"Recovered ${report.recovered} of ${report.unresolved} · ${report.failed} still unreadable$beforeJoining"
stringResource(Res.string.recovered_of_still_unreadable, report.recovered, report.unresolved, report.failed, beforeJoining)
report.recovered > 0 ->
"Recovered ${report.recovered} of ${report.unresolved} event(s)$beforeJoining"
else -> "${report.failed} event(s) still unreadable$beforeJoining"
stringResource(Res.string.recovered_of_event_s, report.recovered, report.unresolved, beforeJoining)
else -> stringResource(Res.string.event_s_still_unreadable, report.failed, beforeJoining)
},
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Center

View File

@@ -64,6 +64,8 @@ import mantra.composeapp.generated.resources.creating_new_chat
import mantra.composeapp.generated.resources.direct_message_detail
import mantra.composeapp.generated.resources.direct_message_functionality_will_be_here
import mantra.composeapp.generated.resources.say_what_now
import mantra.composeapp.generated.resources.private_message_to
import mantra.composeapp.generated.resources.private_to
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
@Composable
@@ -242,7 +244,7 @@ fun ChatRoomMessagingScreen(
Text(
modifier = Modifier.weight(1f),
text = "Private to ${chatMessageListViewModel.nameFor(directMessageRecipient.participantPublicKey)}",
text = stringResource(Res.string.private_to, chatMessageListViewModel.nameFor(directMessageRecipient.participantPublicKey)),
color = MaterialTheme.colorScheme.onPrimaryContainer,
style = MaterialTheme.typography.labelMedium
)
@@ -284,7 +286,7 @@ fun ChatRoomMessagingScreen(
placeholder = {
Text(
text = if (directMessageRecipient != null) {
"Private message to ${chatMessageListViewModel.nameFor(directMessageRecipient.participantPublicKey)}"
stringResource(Res.string.private_message_to, chatMessageListViewModel.nameFor(directMessageRecipient.participantPublicKey))
} else {
stringResource(Res.string.say_what_now)
}

View File

@@ -95,6 +95,10 @@ import mantra.composeapp.generated.resources.the_group_has_a_shared_key
import mantra.composeapp.generated.resources.this_is_fixed_once_the_ceremony_runs
import mantra.composeapp.generated.resources.try_again
import mantra.composeapp.generated.resources.your_share_of_it_is_on_this_device_only_your
import mantra.composeapp.generated.resources.everyone_has_to_be_online_at_the_same_time
import mantra.composeapp.generated.resources.of
import mantra.composeapp.generated.resources.of_members_will_be_needed_to_sign_with_this
import mantra.composeapp.generated.resources.shared_key_for
/**
* The shared-key ceremony: a ChillDKG ritual run across the group's NIP-17
@@ -148,7 +152,7 @@ fun DkgRitualScreen(
TopAppBar(
title = {
Text(
text = "Shared key for ${dkgRitualUIState.localChatRoom.chatRoom.subject ?: "this group"}",
text = stringResource(Res.string.shared_key_for, dkgRitualUIState.localChatRoom.chatRoom.subject ?: "this group"),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
@@ -222,7 +226,7 @@ fun DkgRitualScreen(
actions = {
Text(
modifier = Modifier.padding(start = MaterialTheme.spacing.space200),
text = "${dkgRitualViewModel.threshold.value} of $participantCount",
text = stringResource(Res.string.of, dkgRitualViewModel.threshold.value, participantCount),
style = MaterialTheme.typography.labelLarge
)
}
@@ -245,7 +249,7 @@ fun DkgRitualScreen(
)
Text(
text = "Everyone has to be online at the same time — the ceremony can only finish once all $participantCount of you have taken part.",
text = stringResource(Res.string.everyone_has_to_be_online_at_the_same_time, participantCount),
style = MaterialTheme.typography.bodyMedium
)
@@ -369,7 +373,7 @@ private fun RitualProgress(
}
Text(
text = "${session.threshold} of $participantCount members will be needed to sign with this key.",
text = stringResource(Res.string.of_members_will_be_needed_to_sign_with_this, session.threshold, participantCount),
style = MaterialTheme.typography.bodyMedium
)
@@ -606,7 +610,7 @@ private fun RitualStep(
Text(text = title, style = MaterialTheme.typography.titleSmall)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "$count of $total",
text = stringResource(Res.string.of, count, total),
style = MaterialTheme.typography.labelLarge
)
}
@@ -651,7 +655,7 @@ private fun QuorumStepper(
}
Text(
text = "$threshold of $participantCount",
text = stringResource(Res.string.of, threshold, participantCount),
style = MaterialTheme.typography.titleMedium
)

View File

@@ -68,6 +68,7 @@ import mantra.composeapp.generated.resources.ready_to_sign
import mantra.composeapp.generated.resources.sign
import mantra.composeapp.generated.resources.sign_with_the_group_s_key
import mantra.composeapp.generated.resources.signed_their_part
import mantra.composeapp.generated.resources.events_signed_together
/**
* One signing session, and the member's decision about it.
@@ -352,7 +353,7 @@ private fun WhatIsBeingSigned(events: List<Event>, expected: Int) {
Column(verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space200)) {
if (events.size > 1) {
Text(
text = "${events.size} events, signed together",
text = stringResource(Res.string.events_signed_together, events.size),
style = MaterialTheme.typography.labelMedium
)
}

View File

@@ -14,6 +14,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import press.mantra.compose.ui.theme.spacing
import mantra.composeapp.generated.resources.Res
import org.jetbrains.compose.resources.stringResource
import mantra.composeapp.generated.resources.functionality_coming_soon_2
@Composable
fun ImplementationPendingScreen(
@@ -30,7 +33,7 @@ fun ImplementationPendingScreen(
verticalArrangement = Arrangement.Center
) {
Text(
"\"$text\" functionality coming soon",
stringResource(Res.string.functionality_coming_soon_2, text),
)
}

View File

@@ -50,6 +50,7 @@ import mantra.composeapp.generated.resources.key_package_management
import mantra.composeapp.generated.resources.publish_new_key_package
import mantra.composeapp.generated.resources.something_went_wrong
import mantra.composeapp.generated.resources.we_couldn_t_find_the_local_profile_please
import mantra.composeapp.generated.resources.key_packages
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
@Composable
@@ -135,7 +136,7 @@ fun KeyPackageManagementScreen(
}
item {
Text(
"${keyPackageManagementUIState.keyPackageBundles.size} Key Packages"
stringResource(Res.string.key_packages, keyPackageManagementUIState.keyPackageBundles.size)
)
}

View File

@@ -62,6 +62,7 @@ import mantra.composeapp.generated.resources.proposals
import mantra.composeapp.generated.resources.review
import mantra.composeapp.generated.resources.this_group_has_not_been_asked_to_sign
import mantra.composeapp.generated.resources.waiting_for_you
import mantra.composeapp.generated.resources.of_them_could_not_be_read
/**
* Everything the group has asked its shared key to sign.
@@ -340,7 +341,7 @@ private fun ProposalCard(
// rather than left for the screen behind it.
if (proposal.unreadable > 0) {
Text(
text = "${proposal.unreadable} of them could not be read",
text = stringResource(Res.string.of_them_could_not_be_read, proposal.unreadable),
color = if (proposal.awaitsYou) {
cardContentColor
} else {

View File

@@ -61,6 +61,7 @@ import mantra.composeapp.generated.resources.no_events_were_found
import mantra.composeapp.generated.resources.profiles
import mantra.composeapp.generated.resources.search
import mantra.composeapp.generated.resources.something_went_wrong
import mantra.composeapp.generated.resources.searching_for_on
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
@@ -273,7 +274,7 @@ fun SearchResultScreen(
modifier = Modifier.height(MaterialTheme.spacing.space600)
)
Text(
text = "Searching for ${searchViewModel.searchResultType.name.lowercase()} on \"${searchQuery.lowercase()}\"",
text = stringResource(Res.string.searching_for_on, searchViewModel.searchResultType.name.lowercase(), searchQuery.lowercase()),
textAlign = TextAlign.Center
)
Spacer(

View File

@@ -50,6 +50,9 @@ import mantra.composeapp.generated.resources.no_one_selected_yet
import mantra.composeapp.generated.resources.no_one_to_add_yet
import mantra.composeapp.generated.resources.search_for_people_and_chat_with_them_first
import mantra.composeapp.generated.resources.you_can_still_carry_on_and_invite_people
import mantra.composeapp.generated.resources.add_people_to
import mantra.composeapp.generated.resources.next_with
import mantra.composeapp.generated.resources.selected
/**
* Second step of group creation: pick who is in the group.
@@ -101,7 +104,7 @@ fun SelectChatRoomMembersScreen(
TopAppBar(
title = {
Text(
text = "Add people to $name",
text = stringResource(Res.string.add_people_to, name),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
@@ -125,7 +128,7 @@ fun SelectChatRoomMembersScreen(
Text(
text = if (selectedCount > 0) {
"Next with $selectedCount"
stringResource(Res.string.next_with, selectedCount)
} else {
stringResource(Res.string.next)
}
@@ -136,7 +139,7 @@ fun SelectChatRoomMembersScreen(
Text(
modifier = Modifier.padding(start = MaterialTheme.spacing.space200),
text = if (selectedCount > 0) {
"$selectedCount selected"
stringResource(Res.string.selected, selectedCount)
} else {
stringResource(Res.string.no_one_selected_yet)
},
@@ -247,7 +250,7 @@ fun SelectChatRoomMembersScreen(
)
Text(
text = "Add people to $name",
text = stringResource(Res.string.add_people_to, name),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
)

View File

@@ -65,6 +65,11 @@ import mantra.composeapp.generated.resources.just_you_for_now
import mantra.composeapp.generated.resources.open_chat
import mantra.composeapp.generated.resources.this_decides_who_can_change_the_group_later
import mantra.composeapp.generated.resources.you_and_1_other
import mantra.composeapp.generated.resources.how_should_be_run
import mantra.composeapp.generated.resources.of
import mantra.composeapp.generated.resources.was_created_but_couldn_t_be_added_yet_invite
import mantra.composeapp.generated.resources.was_created_but_its_shared_key_ceremony
import mantra.composeapp.generated.resources.you_and_others
/**
* Last step of group creation: convenient (one admin) or robust (everyone
@@ -127,7 +132,7 @@ fun SelectChatRoomTypeScreen(
TopAppBar(
title = {
Text(
text = "How should $name be run?",
text = stringResource(Res.string.how_should_be_run, name),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
@@ -170,7 +175,7 @@ fun SelectChatRoomTypeScreen(
text = when (selectChatRoomTypeUIState.members.size) {
0 -> stringResource(Res.string.just_you_for_now)
1 -> stringResource(Res.string.you_and_1_other)
else -> "You and ${selectChatRoomTypeUIState.members.size} others"
else -> stringResource(Res.string.you_and_others, selectChatRoomTypeUIState.members.size)
},
style = MaterialTheme.typography.labelLarge
)
@@ -196,7 +201,7 @@ fun SelectChatRoomTypeScreen(
) {
Text(
modifier = Modifier.padding(MaterialTheme.spacing.space200),
text = "$name was created, but ${membersNotAdded.joinToString { selectChatRoomTypeViewModel.displayNameFor(it) }} couldn't be added yet. Invite them again from the chat once they're on Torch.",
text = stringResource(Res.string.was_created_but_couldn_t_be_added_yet_invite, name, membersNotAdded.joinToString { selectChatRoomTypeViewModel.displayNameFor(it) }),
style = MaterialTheme.typography.bodyMedium
)
}
@@ -212,7 +217,7 @@ fun SelectChatRoomTypeScreen(
) {
Text(
modifier = Modifier.padding(MaterialTheme.spacing.space200),
text = "$name was created, but its shared key ceremony couldn't be started. Open the chat and start it from the group's details — until then the group has no key of its own.",
text = stringResource(Res.string.was_created_but_its_shared_key_ceremony, name),
style = MaterialTheme.typography.bodyMedium
)
}
@@ -287,7 +292,7 @@ fun SelectChatRoomTypeScreen(
)
Text(
text = "How should $name be run?",
text = stringResource(Res.string.how_should_be_run, name),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
)
@@ -428,7 +433,7 @@ private fun QuorumPicker(
}
Text(
text = "$quorum of $adminCount",
text = stringResource(Res.string.of, quorum, adminCount),
style = MaterialTheme.typography.titleMedium
)

View File

@@ -55,6 +55,8 @@ import mantra.composeapp.generated.resources.no_chapters
import mantra.composeapp.generated.resources.propose
import mantra.composeapp.generated.resources.this_group_has_no_shared_key_so_it_cannot
import mantra.composeapp.generated.resources.translation_detail
import mantra.composeapp.generated.resources.chapter
import mantra.composeapp.generated.resources.chunks_translated
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -217,9 +219,9 @@ fun TranslationArtifactVersionDetailScreen(
contentDescription = "Chapter"
)
},
headlineContent = { Text("Chapter ${progress.chapter.index}") },
headlineContent = { Text(stringResource(Res.string.chapter, progress.chapter.index)) },
supportingContent = {
Text("${progress.translatedChunks}/${progress.totalChunks} chunks translated")
Text(stringResource(Res.string.chunks_translated, progress.translatedChunks, progress.totalChunks))
}
)
}

View File

@@ -55,6 +55,7 @@ import mantra.composeapp.generated.resources.type_out_what_you_would_like_to_pub
import mantra.composeapp.generated.resources.what_s_your_comment_on_the_below
import mantra.composeapp.generated.resources.what_s_your_reply_to_the_above
import mantra.composeapp.generated.resources.what_vibrations_do_you_want_to_send_out
import mantra.composeapp.generated.resources.replying_to
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalFoundationApi::class)
@Composable
@@ -130,7 +131,7 @@ fun WriteNewNoteScreen(
bottom = MaterialTheme.spacing.space125,
),
text = "replying To ${writeNewNoteUIState.inReplyToNostrEvent.profile?.humanReadableNameOrPubkey() ?: "note"}",
text = stringResource(Res.string.replying_to, writeNewNoteUIState.inReplyToNostrEvent.profile?.humanReadableNameOrPubkey() ?: "note"),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
@@ -346,7 +347,7 @@ fun WriteNewNoteScreen(
if (writeNewNoteUIState.inReplyToNostrEvent != null) {
Text(
text = "replying To ${writeNewNoteUIState.inReplyToNostrEvent.profile?.humanReadableNameOrPubkey() ?: "note"}",
text = stringResource(Res.string.replying_to, writeNewNoteUIState.inReplyToNostrEvent.profile?.humanReadableNameOrPubkey() ?: "note"),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)

View File

@@ -32,6 +32,10 @@ import co.touchlab.kermit.Logger
import coil3.compose.AsyncImage
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import press.mantra.compose.ui.theme.spacing
import mantra.composeapp.generated.resources.Res
import org.jetbrains.compose.resources.stringResource
import mantra.composeapp.generated.resources.nostr
import mantra.composeapp.generated.resources.nostr_2
@Composable
fun RichContent(
@@ -284,7 +288,7 @@ fun RichContent(
)
} else {
Text(
text = "nostr:${segment.eventId.take(8)}..",
text = stringResource(Res.string.nostr, segment.eventId.take(8)),
style = style,
color = MaterialTheme.colorScheme.primary
)
@@ -308,7 +312,7 @@ fun RichContent(
)
} else {
Text(
text = "nostr:${segment.dTag.take(12)}...",
text = stringResource(Res.string.nostr_2, segment.dTag.take(12)),
style = style,
color = MaterialTheme.colorScheme.primary
)

View File

@@ -15,6 +15,7 @@ import press.mantra.compose.ui.theme.spacing
import mantra.composeapp.generated.resources.Res
import org.jetbrains.compose.resources.stringResource
import mantra.composeapp.generated.resources.unsupported_event_kind
import mantra.composeapp.generated.resources.unsupported_event_kind_2
@Composable
internal fun UnsupportedKindBadge(kind: Int?, style: TextStyle) {
@@ -27,7 +28,7 @@ internal fun UnsupportedKindBadge(kind: Int?, style: TextStyle) {
.padding(vertical = MaterialTheme.spacing.space75)
) {
Text(
text = if (kind != null) "Unsupported event kind: $kind" else stringResource(Res.string.unsupported_event_kind),
text = if (kind != null) stringResource(Res.string.unsupported_event_kind_2, kind) else stringResource(Res.string.unsupported_event_kind),
style = style,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(MaterialTheme.spacing.space175)

View File

@@ -54,6 +54,7 @@ import org.jetbrains.compose.resources.stringResource
import mantra.composeapp.generated.resources.be_the_first_to_comment
import mantra.composeapp.generated.resources.post
import mantra.composeapp.generated.resources.something_went_wrong
import mantra.composeapp.generated.resources.reply_to
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
@@ -138,7 +139,7 @@ fun TextNoteEventDetail(
Text(
modifier = Modifier.weight(1f),
text = "Reply to ${localNostrEvent.profile?.humanReadableNameOrPubkey() ?: "the above"}",
text = stringResource(Res.string.reply_to, localNostrEvent.profile?.humanReadableNameOrPubkey() ?: "the above"),
maxLines = 1,
)

View File

@@ -104,6 +104,10 @@ import mantra.composeapp.generated.resources.review
import mantra.composeapp.generated.resources.something_went_wrong
import mantra.composeapp.generated.resources.waiting_for_your_signature
import mantra.composeapp.generated.resources.you
import mantra.composeapp.generated.resources.private_to
import mantra.composeapp.generated.resources.proposals_are_waiting_for_your_signature
import mantra.composeapp.generated.resources.reply_privately_to
import mantra.composeapp.generated.resources.sent_a_private_message_to
class ChatMessageListViewModel(
initialChatMessageListUIState: ChatMessageListUIState,
@@ -668,7 +672,7 @@ class ChatMessageListViewModel(
)
Text(
text = if (localChatMessage.chatMessage.isUserMessage) {
"Private to ${nameFor(localChatMessage.chatMessage.directMessageRecipientPublicKey)}"
stringResource(Res.string.private_to, nameFor(localChatMessage.chatMessage.directMessageRecipientPublicKey))
} else {
stringResource(Res.string.private_to_you)
},
@@ -733,7 +737,7 @@ class ChatMessageListViewModel(
) {
DropdownMenuItem(
text = {
Text("Reply privately to ${nameFor(localChatMessage.chatMessage.senderPublicKey)}")
Text(stringResource(Res.string.reply_privately_to, nameFor(localChatMessage.chatMessage.senderPublicKey)))
},
leadingIcon = {
Icon(Icons.Default.Lock, contentDescription = Decorative)
@@ -887,7 +891,7 @@ private fun ProposalsAwaitingYouNotice(
text = if (single != null) {
stringResource(Res.string.waiting_for_your_signature)
} else {
"${proposals.size} proposals are waiting for your signature"
stringResource(Res.string.proposals_are_waiting_for_your_signature, proposals.size)
},
style = MaterialTheme.typography.labelMedium
)
@@ -948,7 +952,7 @@ private fun PrivateMessageNotice(
)
Text(
text = "$sender sent a private message to $recipient",
text = stringResource(Res.string.sent_a_private_message_to, sender, recipient),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.labelSmall
)