From 419504c982d0311ea6d0f00ae3098d8828a2ab84 Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Tue, 8 Sep 2026 01:38:31 +0200 Subject: [PATCH] refactor: move 315 UI strings into the resource catalogue, and prove the escapes survive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4, second step, of docs/material-design-conformance.md. 251 distinct strings, 315 call sites, from literals inside composables to `stringResource(Res.string.…)`. Literals in composables go 332 -> 76; `stringResource` goes 0 -> 374. **The extractor took four attempts, and each failure is why it is checked in.** *A bare `text = "…"` is not a Compose string.* `text` is an ordinary parameter name and this tree uses it on data classes: `NavigationUIState.Loading(text = "…")` is not a composable, and rewriting it failed with "@Composable invocations can only happen from the context of a @Composable function". So `Text(`/`BasicText(` calls are brace-matched and only literals genuinely inside one are touched. *A regex over quote pairs is not a Kotlin lexer.* Matching `"[^"]*"` over a whole file pairs one string's closing quote with the next string's opening quote, so "literals" came out as several lines of Kotlin. Restricting the body to one line fixed that and left a subtler version: `"a ${if (n == 1) "chunk" else "chunks"} b"` has two inner literals belonging to an outer template, and left-to-right matching lifts them out as strings of their own. The script decided `"chunk"` and `"note"` were UI text worth translating. It now scans properly -- on an opening quote, walk forward tracking `${` depth, recursing over nested literals, and stop at the closing quote at depth zero. *A fragment is not a string.* `"a " + x + " b"` is one sentence in three pieces, and " b" is not something a translator can work with -- word order differs between languages. Three filters, because the fragments hide in three shapes: adjacent to a `+`, leading or trailing whitespace or no letters at all (", " and ":"), and -- the one that needed a fourth pass -- a pluralisation where the *parenthesis* is adjacent to the `+` and neither literal is: (if (proposal.eventCount == 2) "event" else "events") + Testing the line rather than the literal catches those four sites while leaving a genuine either/or alone: `if (session == null) "Start key ceremony" else "Try again"` has no `+` and both branches are whole strings. **Compose Resources is not aapt, and that was a bug this commit nearly shipped.** The first version escaped apostrophes as `\'` and doubled `%`, which is what android's resource compiler requires. Compose Resources does neither. `getString(Res.string.don_t_sign)` returned Don\'t sign backslash included, and there are 30-odd apostrophes in this catalogue. Every one of them would have rendered with a visible backslash, on screens nobody opens often. What makes this worth a permanent test rather than a fixed script: escape handling is **partial**, not absent. The same run showed `\n` *is* processed -- "Currently no messages have been shared.\nBreak the ice." comes back with a real newline. So there is no family rule to rely on, and the next escape somebody adds needs checking on its own. `StringCatalogueJvmTest` asserts all three cases through `getString`, which is the non-composable reader for the same resources and needs no composition. It found the bug before a device did. **Names are derived from content**, snake_cased and truncated at a word boundary: `something_went_wrong`, `add_artifact_to_the_group_library`. The conventional shape for an automated extraction, with a known cost -- rewording the copy leaves the name slightly stale. The alternative, naming by *purpose*, needs somebody to read 315 call sites, and a name asserting the wrong purpose is worse than one that is a little dated. **1101 dead strings out, 251 live ones in.** The catalogue previously held the phoenix wallet fork's entire string table with nothing referencing it; it now holds this app's own, plus `app_name`. **What is left, and why.** 76 literals: 46 interpolated, which need format placeholders and an argument order decided per site, and 30 concatenation fragments, which need their sentences reassembled first. Both are the next commit, and both are jobs where a script should not guess. **Tests.** 947 pass, 598 jvm over 73 classes and 349 android over 44, up from 944/595/349 -- three new assertions in one new class. `:composeApp:compileDebugKotlinAndroid` builds, the debug apk installs and runs on emulator-5554 with its text reading correctly through onboarding and the message list. `m3-audit.sh --check` exits 0. `ChronicleApplyJvmTest` failed once during this commit's verification and passed on rerun; it is the pre-existing 1-in-8 flake filed during phase 3, and nothing here touches chronicle code. Co-Authored-By: Claude Opus 5 --- .../composeResources/values/strings.xml | 251 ++++++++++++++++ .../ui/composable/ActiveProfileScreen.kt | 32 ++- .../ui/composable/AddArtifactScreen.kt | 38 ++- .../compose/ui/composable/AddChapterScreen.kt | 23 +- .../compose/ui/composable/AddDialectScreen.kt | 29 +- .../AddMemberToChatRoomConfirmationScreen.kt | 11 +- .../AddTranslationArtifactVersionScreen.kt | 14 +- .../ui/composable/ArtifactDetailScreen.kt | 35 ++- .../ui/composable/ChapterDetailScreen.kt | 11 +- .../ui/composable/ChatRoomCreationScreen.kt | 23 +- .../ui/composable/ChatRoomDetailScreen.kt | 56 ++-- .../ui/composable/ChatRoomMessagingScreen.kt | 14 +- .../ui/composable/CreateProfileScreen.kt | 66 +++-- .../ui/composable/DkgApprovalScaffold.kt | 8 +- .../compose/ui/composable/DkgRitualScreen.kt | 48 +++- .../ui/composable/DkgRound1ApprovalScreen.kt | 8 +- .../ui/composable/DkgRound2ApprovalScreen.kt | 5 +- .../ui/composable/FrostSigningScreen.kt | 26 +- .../compose/ui/composable/HomeScreen.kt | 11 +- .../composable/KeyPackageManagementScreen.kt | 14 +- .../compose/ui/composable/LandingScreen.kt | 23 +- .../ui/composable/NostrEventDetailScreen.kt | 14 +- .../ui/composable/ProposalListScreen.kt | 17 +- .../SearchMemberToAddToChatRoomScreen.kt | 11 +- .../ui/composable/SearchResultScreen.kt | 14 +- .../compose/ui/composable/SearchScreen.kt | 19 +- .../composable/SelectChatRoomMembersScreen.kt | 17 +- .../ui/composable/SelectChatRoomTypeScreen.kt | 20 +- .../ui/composable/ShareProfileScreen.kt | 14 +- .../compose/ui/composable/SignInScreen.kt | 36 ++- .../ui/composable/SocialPreconditionScreen.kt | 20 +- .../SovereignWalletStartupScreen.kt | 11 +- .../ui/composable/TranslateChunkScreen.kt | 22 +- .../TranslationArtifactVersionDetailScreen.kt | 20 +- .../ui/composable/TranslationChapterScreen.kt | 10 +- .../ui/composable/UnannouncedProfileScreen.kt | 8 +- .../ui/composable/UnindexedProfileScreen.kt | 8 +- .../ui/composable/UnqueuedProfileScreen.kt | 8 +- .../UnqueuedProfileSynchronizationScreen.kt | 8 +- .../ui/composable/UnsignedProfileScreen.kt | 8 +- .../ui/composable/UnsyncedProfileScreen.kt | 8 +- .../ui/composable/WriteNewNoteScreen.kt | 26 +- .../composable/widgets/content/ArticleCard.kt | 11 +- .../widgets/content/ImageWithContextMenu.kt | 11 +- .../widgets/content/LightningInvoiceCard.kt | 28 +- .../widgets/content/LiveStreamCardContent.kt | 14 +- .../widgets/content/QuotedAddressableNote.kt | 5 +- .../composable/widgets/content/QuotedNote.kt | 5 +- .../widgets/content/UnsupportedKindBadge.kt | 5 +- .../widgets/detail/MetadataEventDetail.kt | 23 +- .../widgets/detail/TextNoteEventDetail.kt | 11 +- .../dialogs/NewChatBottomSheetDialog.kt | 14 +- .../StartDirectMessageToNpubOrNip05Dialog.kt | 11 +- .../composable/widgets/feed/EventListView.kt | 11 +- .../widgets/feed/TextNoteFeedItem.kt | 8 +- .../ui/view/model/ChatMessageListViewModel.kt | 28 +- .../ui/view/model/ChatRoomListViewModel.kt | 11 +- .../ui/view/model/FeedListViewModel.kt | 11 +- .../ui/view/model/FollowersListViewModel.kt | 11 +- .../ui/view/model/FollowingListViewModel.kt | 11 +- .../ui/view/model/InReplyToViewModel.kt | 11 +- .../compose/ui/StringCatalogueJvmTest.kt | 48 ++++ docs/scripts/m3-extract-strings.py | 267 ++++++++++++++++++ 63 files changed, 1306 insertions(+), 314 deletions(-) create mode 100644 composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/StringCatalogueJvmTest.kt create mode 100755 docs/scripts/m3-extract-strings.py diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index bc768f2f..9a3def24 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -21,4 +21,255 @@ "XML file ... is not valid. Check the file content." --> Mantra + Add a dialect the group can translate into + Add artifact to library + Add artifact to the group library + Add chapter + Add dialect + Add to group + Add translation + After this there is no turning back. + All broadcasts are queued so that we can manage data usage on metered connections. + Any amount + ARTICLE + Artifact detail + As long as you control your keys there can be no dispute about who YOU actually is. + Back + Be sure to keep this nsec safe. + Be the first to comment. + Bio + Block user + Cancel + Change account + Chapter detail + Chapter name + Chapter translation + Chapters + Choose who to chat with + Close + Copy URL + Country + Create chat + Create new chat + Create profile + Create project + Create the #admins group + Creating new chat. + Currently no contacts. Please search and chat with a few people. + Currently no messages have been shared.\nBreak the ice. + Delete group + Details + Dialect name + Dialects + Direct message detail + Direct message functionality will be here. + Direct message via npub + Don't sign + Download + Edit profile + eg. Chapter 1 — The Beginning + eg. First Edition + eg. https://harper.com/2-kill-Bird + eg. Lesotho + eg. Sesotho + eg. st + eg. To Kill a Mocking Bird + end this + ENDED + Enter the name you want to use for your group + Enter the name you want to use for your profile + Enter the nsec or npub (read only) that you want to sign in as + Enter the translation for this chunk + Events are indexed so that we can deliver a premium local first experience. + Everything else + Everything is cryptographical sound. Just announcing your profile to the world. + Everything is cryptographical sound. Just indexing your profile on the device. + Everything is cryptographical sound. Just need to queue your profile and announce it to the world. + Expired + ✗ Failed + follow + follow back + functionality coming soon. + Has not taken part yet + How many admins have to approve a change? + How many members will it take to sign? + If you are new to Torch or just want to create a fresh profile + Initial version label + Input npub... or nip05 + Introduce yourself + Invite + Invite a friend + Invite new member + It is cryptographical secure, and decentralized, putting you in total control of your digital profile + Just you for now + Keep the feed alive. + Key + Key package management + Language + Learn more + Leave group + Library + Lightning invoice + LIVE + Live stream + Loading + Loading article... + Loading author information + Loading author information... + loading information... + Loading note... + Loading stream... + Lock prompt coming soon. + Malformed note + Mantra + Members + members + Name + Name (eg. Alan Turing) + Name (eg. Group Discussions) + Name of artifact + Network relays + New chat + Next + No artifacts exists in this groups library. + No chapters. + No chapters yet. + No chat message relays were found for this user. + No chunks. + No dialects have been defined in this group yet. Add one from the group's detail screen first. + No dialects have been defined in this group. + No events were found + No messages. Go to a profile and send them a message. + No one selected yet + No one to add yet. + No projects exists in this group. + No translations yet. + No versions. + Not now + Nothing was created and no key exists. It is safe to run it again. + nsec, npub, nip-05 (static address) + Open chat + Original + Original text + Original text (markdown) + ✓ Paid + Paste the chapter's markdown. Blank lines separate paragraphs into chunks. + Pay + Pay now + Post + post functionality coming soon. + Private to you + Profile + Profile is ready + Profile keys + Profiles + Projects + Proposals + Propose + Propose artifact + Propose chapter + Propose dialect + Propose translation + Publish new key package + Re-broadcast + Read to the end of the list to sign. + Ready to sign + Recents + Reindex events + reposted + Review + Review and confirm + Review and contribute + Review and join + Say what now? + Search + Search for people and chat with them first — everyone you know locally shows up here. + Search hashtags + Search member functionality + Search message functionality will be here. + See how deep the rabbit hole goes + Select a wallet + Send message + Share profile + Shared key + Sign + Sign in + Sign in to nsec + Sign in to Torch via nsec, or remote signer + Sign in with an npub + Sign out + Sign with the group's key + Signed their part + Skip for now + Something went wrong + Something went wrong and we were unable to sign in to your provided profile. Please try again later. + Something went wrong and we were unable to sign you up. Please try again later. + Something went wrong and we were unable to write a new note. Please try again later. + Source dialect + Start + Start a group chat + Start chat + Start chat via npub or nip05 + Start key ceremony + Startup error + Taking part with these members + Tap to load + Tell friends to join you so your feed stays lively and fresh. + The above will be your new note. + The above will be your profile. + The ceremony was abandoned. + The group can hold one key together, split so that no single member holds it. Signing with it takes a quorum. + The group has a shared key. + This artifact has no version for a translation to be of. + This artifact has no version for a chapter to attach to. + This chapter has no chunks. + This decides who can change the group later. You can't switch afterwards. + This group has no shared key, so it cannot sign them in. + This group has not been asked to sign anything yet. + This is fixed once the ceremony runs. Changing it later means generating a new key. + This is the last thing the ceremony needs from you. + This will be shown when people open the chat for more details. + This will be shown when people open your profile. + This will be the display name for this chat room. + This will be the display name for your profile and also important for search. + This will give you read only access to the profile. + This will give you write access to the profile. + Torch will be broadcast what you publish to a distributed set of relays so that it's decentralized. + Translate chunk + Translate into which dialect? + Translated text + Translation + Translation detail + Translations + Transmit note + Trending notes functionality coming soon. In the meantime search for what you are looking for. + Try again + Type out what you would like to publish + Unfollow + Unsupported event kind + Untitled article + Url + Version + Versions + View and accept invites you may have received to stay connected with others. + View invites + Waiting for you + Waiting for your signature + We are looking for your profile on as many relays as possible. Nostr aims to be decentralized by distributing data to multiple nodse. + We are searching the internet to find your profile and complete sign in. + We couldn't find the local profile. Please try again later. + We couldn't find your nostr event. Please try again later. + What's your comment on the below + What's your reply to the above + What should people know about you? + What vibrations do you want to send out? + What will be discussed in this chat room. + Who will you be passing the aux to? + You + You and 1 other + You are about to create a NOSTR profile. + You can still carry on and invite people later. + You will be in full control of this profile. If you would like to use it for the long term please remember to backup the profile/keys. + Your profile is almost ready... just getting it's first cryptographic signature together. + Your share of it is on this device only. Your wallet backup restores it — nobody else's share can. diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ActiveProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ActiveProfileScreen.kt index 43b167fc..5157d5a8 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ActiveProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ActiveProfileScreen.kt @@ -52,6 +52,18 @@ import press.mantra.compose.ui.view.state.ActiveProfileUIState import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.change_account +import mantra.composeapp.generated.resources.edit_profile +import mantra.composeapp.generated.resources.key_package_management +import mantra.composeapp.generated.resources.network_relays +import mantra.composeapp.generated.resources.profile +import mantra.composeapp.generated.resources.profile_keys +import mantra.composeapp.generated.resources.share_profile +import mantra.composeapp.generated.resources.sign_out +import mantra.composeapp.generated.resources.something_went_wrong +import mantra.composeapp.generated.resources.we_couldn_t_find_the_local_profile_please @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -80,7 +92,7 @@ fun ActiveProfileScreen( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text("Something went wrong") + Text(stringResource(Res.string.something_went_wrong)) } } is ActiveProfileUIState.Loaded -> { @@ -89,7 +101,7 @@ fun ActiveProfileScreen( TopAppBar( title = { Text( - text = "Profile" + text = stringResource(Res.string.profile) ) }, navigationIcon = { @@ -177,7 +189,7 @@ fun ActiveProfileScreen( ) Text( - "Edit profile" + stringResource(Res.string.edit_profile) ) } @@ -204,7 +216,7 @@ fun ActiveProfileScreen( modifier = Modifier.width(MaterialTheme.spacing.space125) ) Text( - "Share profile" + stringResource(Res.string.share_profile) ) } @@ -236,7 +248,7 @@ fun ActiveProfileScreen( ) Text( - "Key package management" + stringResource(Res.string.key_package_management) ) } } @@ -261,7 +273,7 @@ fun ActiveProfileScreen( ) Text( - "Change account" + stringResource(Res.string.change_account) ) } } @@ -285,7 +297,7 @@ fun ActiveProfileScreen( modifier = Modifier.width(MaterialTheme.spacing.space125) ) - Text("Profile keys") + Text(stringResource(Res.string.profile_keys)) } } @@ -308,7 +320,7 @@ fun ActiveProfileScreen( ) Text( - "Network relays" + stringResource(Res.string.network_relays) ) } } @@ -331,7 +343,7 @@ fun ActiveProfileScreen( ) Text( - "Sign out" + stringResource(Res.string.sign_out) ) } } @@ -352,7 +364,7 @@ fun ActiveProfileScreen( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text("We couldn't find the local profile. Please try again later.") + Text(stringResource(Res.string.we_couldn_t_find_the_local_profile_please)) } } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt index dfe07bd9..c029fb2c 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt @@ -69,6 +69,20 @@ import press.mantra.compose.ui.composable.navigation.routes.ImplementationPendin import press.mantra.compose.ui.view.model.AddArtifactViewModel import press.mantra.compose.ui.view.state.AddArtifactUIState import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.add_artifact_to_the_group_library +import mantra.composeapp.generated.resources.direct_message_detail +import mantra.composeapp.generated.resources.direct_message_functionality_will_be_here +import mantra.composeapp.generated.resources.eg_first_edition +import mantra.composeapp.generated.resources.eg_https_harper_com_2_kill_bird +import mantra.composeapp.generated.resources.eg_to_kill_a_mocking_bird +import mantra.composeapp.generated.resources.initial_version_label +import mantra.composeapp.generated.resources.name_of_artifact +import mantra.composeapp.generated.resources.no_dialects_have_been_defined_in_this_group +import mantra.composeapp.generated.resources.propose_artifact +import mantra.composeapp.generated.resources.source_dialect +import mantra.composeapp.generated.resources.url @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @Composable @@ -205,7 +219,7 @@ fun AddArtifactScreen( Icons.Default.Add, contentDescription = "Propose artifact" ) - Text("Propose artifact") + Text(stringResource(Res.string.propose_artifact)) } } ) @@ -220,7 +234,7 @@ fun AddArtifactScreen( verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space125), horizontalAlignment = Alignment.CenterHorizontally ) { - Text("Add artifact to the group library") + Text(stringResource(Res.string.add_artifact_to_the_group_library)) if (!addArtifactUIState.canSign) { Text( @@ -248,12 +262,12 @@ fun AddArtifactScreen( }, label = { Text( - text = "Name of artifact" + text = stringResource(Res.string.name_of_artifact) ) }, placeholder = { Text( - text = "eg. To Kill a Mocking Bird" + text = stringResource(Res.string.eg_to_kill_a_mocking_bird) ) }, ) @@ -275,12 +289,12 @@ fun AddArtifactScreen( }, label = { Text( - text = "Url" + text = stringResource(Res.string.url) ) }, placeholder = { Text( - text = "eg. https://harper.com/2-kill-Bird" + text = stringResource(Res.string.eg_https_harper_com_2_kill_bird) ) }, ) @@ -302,21 +316,21 @@ fun AddArtifactScreen( }, label = { Text( - text = "Initial version label" + text = stringResource(Res.string.initial_version_label) ) }, placeholder = { Text( - text = "eg. First Edition" + text = stringResource(Res.string.eg_first_edition) ) }, ) - Text("Source dialect") + Text(stringResource(Res.string.source_dialect)) if (addArtifactUIState.dialects.isEmpty()) { Text( - text = "No dialects have been defined in this group yet. Add one from the group's detail screen first.", + text = stringResource(Res.string.no_dialects_have_been_defined_in_this_group), style = MaterialTheme.typography.bodySmall ) } else { @@ -352,13 +366,13 @@ fun AddArtifactScreen( modifier = Modifier.weight(1f) ) Text( - text = "Direct message detail", + text = stringResource(Res.string.direct_message_detail), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) Text( - text = "Direct message functionality will be here.", + text = stringResource(Res.string.direct_message_functionality_will_be_here), style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddChapterScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddChapterScreen.kt index cf5958a1..71290ac7 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddChapterScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddChapterScreen.kt @@ -54,6 +54,15 @@ import press.mantra.compose.ui.theme.TorchTheme import press.mantra.compose.ui.view.model.AddChapterViewModel import press.mantra.compose.ui.view.state.AddChapterUIState import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.add_chapter +import mantra.composeapp.generated.resources.chapter_name +import mantra.composeapp.generated.resources.eg_chapter_1_the_beginning +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 @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -197,7 +206,7 @@ fun AddChapterScreen( Icons.Default.Add, contentDescription = "Propose chapter" ) - Text("Propose chapter") + Text(stringResource(Res.string.propose_chapter)) } } ) @@ -216,7 +225,7 @@ fun AddChapterScreen( ) } else if (artifactVersion == null) { Text( - text = "This artifact has no version for a chapter to attach to.", + text = stringResource(Res.string.this_artifact_has_no_version_for_a_chapter), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.error ) @@ -239,15 +248,15 @@ fun AddChapterScreen( contentDescription = "Name of the chapter" ) }, - label = { Text("Chapter name") }, - placeholder = { Text("eg. Chapter 1 — The Beginning") }, + label = { Text(stringResource(Res.string.chapter_name)) }, + placeholder = { Text(stringResource(Res.string.eg_chapter_1_the_beginning)) }, ) OutlinedTextField( modifier = Modifier.fillMaxWidth().weight(1f), state = originalTextFieldState, - label = { Text("Original text (markdown)") }, - placeholder = { Text("Paste the chapter's markdown. Blank lines separate paragraphs into chunks.") }, + label = { Text(stringResource(Res.string.original_text_markdown)) }, + placeholder = { Text(stringResource(Res.string.paste_the_chapter_s_markdown_blank_lines)) }, ) Text( @@ -276,7 +285,7 @@ fun AddChapterScreen( ) { Spacer(modifier = Modifier.weight(1f)) Text( - text = "Add chapter", + text = stringResource(Res.string.add_chapter), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddDialectScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddDialectScreen.kt index 7df205dd..64d28e0d 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddDialectScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddDialectScreen.kt @@ -56,6 +56,17 @@ import press.mantra.compose.ui.theme.TorchTheme import press.mantra.compose.ui.view.model.AddDialectViewModel import press.mantra.compose.ui.view.state.AddDialectUIState import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.add_a_dialect_the_group_can_translate_into +import mantra.composeapp.generated.resources.add_dialect +import mantra.composeapp.generated.resources.country +import mantra.composeapp.generated.resources.dialect_name +import mantra.composeapp.generated.resources.eg_lesotho +import mantra.composeapp.generated.resources.eg_sesotho +import mantra.composeapp.generated.resources.eg_st +import mantra.composeapp.generated.resources.language +import mantra.composeapp.generated.resources.propose_dialect @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -174,7 +185,7 @@ fun AddDialectScreen( Icons.Default.Add, contentDescription = "Propose dialect" ) - Text("Propose dialect") + Text(stringResource(Res.string.propose_dialect)) } } ) @@ -188,7 +199,7 @@ fun AddDialectScreen( verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space125), horizontalAlignment = Alignment.CenterHorizontally ) { - Text("Add a dialect the group can translate into") + Text(stringResource(Res.string.add_a_dialect_the_group_can_translate_into)) if (!addDialectUIState.canSign) { Text( @@ -216,12 +227,12 @@ fun AddDialectScreen( }, label = { Text( - text = "Dialect name" + text = stringResource(Res.string.dialect_name) ) }, placeholder = { Text( - text = "eg. Sesotho" + text = stringResource(Res.string.eg_sesotho) ) }, ) @@ -243,12 +254,12 @@ fun AddDialectScreen( }, label = { Text( - text = "Country" + text = stringResource(Res.string.country) ) }, placeholder = { Text( - text = "eg. Lesotho" + text = stringResource(Res.string.eg_lesotho) ) }, ) @@ -270,12 +281,12 @@ fun AddDialectScreen( }, label = { Text( - text = "Language" + text = stringResource(Res.string.language) ) }, placeholder = { Text( - text = "eg. st" + text = stringResource(Res.string.eg_st) ) }, ) @@ -296,7 +307,7 @@ fun AddDialectScreen( modifier = Modifier.weight(1f) ) Text( - text = "Add dialect", + text = stringResource(Res.string.add_dialect), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddMemberToChatRoomConfirmationScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddMemberToChatRoomConfirmationScreen.kt index 1670219d..4f90886b 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddMemberToChatRoomConfirmationScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddMemberToChatRoomConfirmationScreen.kt @@ -38,6 +38,11 @@ import press.mantra.compose.ui.view.model.AddMemberToChatRoomConfirmationViewMod import press.mantra.compose.ui.view.state.AddMemberToChatRoomConfirmationUIState 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.direct_message_detail +import mantra.composeapp.generated.resources.direct_message_functionality_will_be_here +import mantra.composeapp.generated.resources.invite @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -135,7 +140,7 @@ fun AddMemberToChatRoomConfirmationScreen( modifier = Modifier.weight(1f) ) - Text("Invite") + Text(stringResource(Res.string.invite)) Text( text = addMemberToChatRoomConfirmationUIState.profile.humanReadableNameOrPubkey() @@ -170,13 +175,13 @@ fun AddMemberToChatRoomConfirmationScreen( modifier = Modifier.weight(1f) ) Text( - text = "Direct message detail", + text = stringResource(Res.string.direct_message_detail), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) Text( - text = "Direct message functionality will be here.", + text = stringResource(Res.string.direct_message_functionality_will_be_here), style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddTranslationArtifactVersionScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddTranslationArtifactVersionScreen.kt index 03b489f6..c8c35fa3 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddTranslationArtifactVersionScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddTranslationArtifactVersionScreen.kt @@ -58,6 +58,12 @@ import press.mantra.compose.ui.theme.TorchTheme import press.mantra.compose.ui.view.model.AddTranslationArtifactVersionViewModel import press.mantra.compose.ui.view.state.AddTranslationArtifactVersionUIState import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +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 @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @Composable @@ -201,7 +207,7 @@ fun AddTranslationArtifactVersionScreen( Icons.Default.Translate, contentDescription = "Propose translation" ) - Text("Propose translation") + Text(stringResource(Res.string.propose_translation)) } } ) @@ -220,13 +226,13 @@ fun AddTranslationArtifactVersionScreen( ) } else if (artifactVersion == null) { Text( - text = "This artifact has no version for a translation to be of.", + text = stringResource(Res.string.this_artifact_has_no_version_for_a), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.error ) } - Text("Translate into which dialect?") + Text(stringResource(Res.string.translate_into_which_dialect)) if (addTranslationUIState.dialects.isEmpty()) { // The only way out of this screen, and it is not on it: @@ -283,7 +289,7 @@ fun AddTranslationArtifactVersionScreen( ) { Spacer(modifier = Modifier.weight(1f)) Text( - text = "Add translation", + text = stringResource(Res.string.add_translation), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt index 816f01b5..13eaf2a4 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt @@ -49,6 +49,19 @@ import press.mantra.compose.ui.theme.TorchTheme import press.mantra.compose.ui.view.model.ArtifactDetailViewModel import press.mantra.compose.ui.view.state.ArtifactDetailUIState import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.add_chapter +import mantra.composeapp.generated.resources.add_translation +import mantra.composeapp.generated.resources.artifact_detail +import mantra.composeapp.generated.resources.chapters +import mantra.composeapp.generated.resources.details +import mantra.composeapp.generated.resources.no_chapters_yet +import mantra.composeapp.generated.resources.no_translations_yet +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 @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -108,12 +121,12 @@ fun ArtifactDetailScreen( // Chapters item { Text( - text = "Chapters", + text = stringResource(Res.string.chapters), style = MaterialTheme.typography.labelMedium ) } if (artifactDetailUIState.chapters.isEmpty()) { - item { Text("No chapters yet.") } + item { Text(stringResource(Res.string.no_chapters_yet)) } } else { items( items = artifactDetailUIState.chapters, @@ -167,7 +180,7 @@ fun ArtifactDetailScreen( contentDescription = "Add chapter" ) Spacer(modifier = Modifier.width(MaterialTheme.spacing.space125)) - Text("Add chapter") + Text(stringResource(Res.string.add_chapter)) } } @@ -176,12 +189,12 @@ fun ArtifactDetailScreen( // Translations item { Text( - text = "Translations", + text = stringResource(Res.string.translations), style = MaterialTheme.typography.labelMedium ) } if (artifactDetailUIState.translations.isEmpty()) { - item { Text("No translations yet.") } + item { Text(stringResource(Res.string.no_translations_yet)) } } else { items( items = artifactDetailUIState.translations, @@ -232,7 +245,7 @@ fun ArtifactDetailScreen( contentDescription = "Add translation" ) Spacer(modifier = Modifier.width(MaterialTheme.spacing.space125)) - Text("Add translation") + Text(stringResource(Res.string.add_translation)) } } @@ -241,7 +254,7 @@ fun ArtifactDetailScreen( // Details item { Text( - text = "Details", + text = stringResource(Res.string.details), style = MaterialTheme.typography.labelMedium ) } @@ -260,12 +273,12 @@ fun ArtifactDetailScreen( // Versions item { Text( - text = "Versions", + text = stringResource(Res.string.versions), style = MaterialTheme.typography.labelMedium ) } if (artifactDetailUIState.versions.isEmpty()) { - item { Text("No versions.") } + item { Text(stringResource(Res.string.no_versions)) } } else { items( items = artifactDetailUIState.versions, @@ -274,7 +287,7 @@ fun ArtifactDetailScreen( Card { ListItem( headlineContent = { Text(version.versionLabel) }, - overlineContent = { Text("Version") } + overlineContent = { Text(stringResource(Res.string.version)) } ) } } @@ -291,7 +304,7 @@ fun ArtifactDetailScreen( ) { Spacer(modifier = Modifier.weight(1f)) Text( - text = "Artifact detail", + text = stringResource(Res.string.artifact_detail), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChapterDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChapterDetailScreen.kt index 7cc799a4..99d5b6fc 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChapterDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChapterDetailScreen.kt @@ -39,6 +39,11 @@ import press.mantra.compose.ui.theme.TorchTheme import press.mantra.compose.ui.view.model.ChapterDetailViewModel import press.mantra.compose.ui.view.state.ChapterDetailUIState import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +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 @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -104,7 +109,7 @@ fun ChapterDetailScreen( // Original text (markdown, shown as-is). item { Text( - text = "Original text", + text = stringResource(Res.string.original_text), style = MaterialTheme.typography.labelMedium ) } @@ -128,7 +133,7 @@ fun ChapterDetailScreen( ) } if (chapterDetailUIState.chunks.isEmpty()) { - item { Text("No chunks.") } + item { Text(stringResource(Res.string.no_chunks)) } } else { items( items = chapterDetailUIState.chunks, @@ -163,7 +168,7 @@ fun ChapterDetailScreen( ) { Spacer(modifier = Modifier.weight(1f)) Text( - text = "Chapter detail", + text = stringResource(Res.string.chapter_detail), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomCreationScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomCreationScreen.kt index a55ac5fb..9be67274 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomCreationScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomCreationScreen.kt @@ -30,6 +30,15 @@ import press.mantra.compose.ui.composable.navigation.routes.Route import press.mantra.compose.ui.view.model.ChatRoomCreationViewModel 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.choose_who_to_chat_with +import mantra.composeapp.generated.resources.enter_the_name_you_want_to_use_for_your +import mantra.composeapp.generated.resources.name_eg_group_discussions +import mantra.composeapp.generated.resources.this_will_be_shown_when_people_open_the_chat +import mantra.composeapp.generated.resources.this_will_be_the_display_name_for_this_chat +import mantra.composeapp.generated.resources.what_should_people_know_about_you +import mantra.composeapp.generated.resources.what_will_be_discussed_in_this_chat_room @Composable fun ChatRoomCreationScreen( @@ -72,13 +81,13 @@ fun ChatRoomCreationScreen( ), label = { Text( - text = "Name (eg. Group Discussions)", + text = stringResource(Res.string.name_eg_group_discussions), maxLines = 1, ) }, placeholder = { Text( - text = "Enter the name you want to use for your group", + text = stringResource(Res.string.enter_the_name_you_want_to_use_for_your), maxLines = 1, ) }, @@ -91,7 +100,7 @@ fun ChatRoomCreationScreen( ) Text( - text = "This will be the display name for this chat room.", + text = stringResource(Res.string.this_will_be_the_display_name_for_this_chat), style = MaterialTheme.typography.labelMedium, textAlign = TextAlign.Center ) @@ -116,14 +125,14 @@ fun ChatRoomCreationScreen( ), label = { Text( - text = "What will be discussed in this chat room.", + text = stringResource(Res.string.what_will_be_discussed_in_this_chat_room), maxLines = 1, ) }, placeholder = { Text( - text = "What should people know about you?", + text = stringResource(Res.string.what_should_people_know_about_you), maxLines = 1, ) }, @@ -136,7 +145,7 @@ fun ChatRoomCreationScreen( ) Text( - text = "This will be shown when people open the chat for more details.", + text = stringResource(Res.string.this_will_be_shown_when_people_open_the_chat), style = MaterialTheme.typography.labelMedium, textAlign = TextAlign.Center ) @@ -151,7 +160,7 @@ fun ChatRoomCreationScreen( } ) { Text( - "Choose who to chat with" + stringResource(Res.string.choose_who_to_chat_with) ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt index 173705a8..aa4b439b 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt @@ -69,6 +69,26 @@ import press.mantra.compose.ui.composable.navigation.routes.AddArtifactRoute import press.mantra.compose.ui.composable.navigation.routes.AddDialectRoute import press.mantra.compose.ui.composable.navigation.routes.ArtifactDetailRoute import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.add_artifact_to_library +import mantra.composeapp.generated.resources.add_dialect +import mantra.composeapp.generated.resources.create_project +import mantra.composeapp.generated.resources.delete_group +import mantra.composeapp.generated.resources.dialects +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_new_member +import mantra.composeapp.generated.resources.leave_group +import mantra.composeapp.generated.resources.library +import mantra.composeapp.generated.resources.members_2 +import mantra.composeapp.generated.resources.no_artifacts_exists_in_this_groups_library +import mantra.composeapp.generated.resources.no_dialects_have_been_defined_in_this_group_2 +import mantra.composeapp.generated.resources.no_projects_exists_in_this_group +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 @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -144,14 +164,14 @@ fun ChatRoomDetailScreen( item { // Artifacts Text( - text = "Library", + text = stringResource(Res.string.library), style = MaterialTheme.typography.labelMedium ) } if (chatRoomDetailUIState.artifacts.isEmpty()) { item { - Text("No artifacts exists in this groups library.") + Text(stringResource(Res.string.no_artifacts_exists_in_this_groups_library)) } } else { items( @@ -217,7 +237,7 @@ fun ChatRoomDetailScreen( modifier = Modifier.width(MaterialTheme.spacing.space125) ) - Text("Add artifact to library") + Text(stringResource(Res.string.add_artifact_to_library)) } } @@ -228,14 +248,14 @@ fun ChatRoomDetailScreen( item { // Dialects Text( - text = "Dialects", + text = stringResource(Res.string.dialects), style = MaterialTheme.typography.labelMedium ) } if (chatRoomDetailUIState.dialects.isEmpty()) { item { - Text("No dialects have been defined in this group.") + Text(stringResource(Res.string.no_dialects_have_been_defined_in_this_group_2)) } } else { items( @@ -283,7 +303,7 @@ fun ChatRoomDetailScreen( modifier = Modifier.width(MaterialTheme.spacing.space125) ) - Text("Add dialect") + Text(stringResource(Res.string.add_dialect)) } } @@ -296,13 +316,13 @@ fun ChatRoomDetailScreen( item { // Artifacts Text( - text = "Projects", + text = stringResource(Res.string.projects), style = MaterialTheme.typography.labelMedium ) } item { - Text("No projects exists in this group.") + Text(stringResource(Res.string.no_projects_exists_in_this_group)) } item { @@ -326,7 +346,7 @@ fun ChatRoomDetailScreen( modifier = Modifier.width(MaterialTheme.spacing.space125) ) - Text("Create project") + Text(stringResource(Res.string.create_project)) } } @@ -336,7 +356,7 @@ fun ChatRoomDetailScreen( item { Text( - text = "members", + text = stringResource(Res.string.members_2), style = MaterialTheme.typography.labelMedium ) } @@ -365,7 +385,7 @@ fun ChatRoomDetailScreen( modifier = Modifier.width(MaterialTheme.spacing.space125) ) - Text("Shared key") + Text(stringResource(Res.string.shared_key)) } } } @@ -399,7 +419,7 @@ fun ChatRoomDetailScreen( modifier = Modifier.width(MaterialTheme.spacing.space125) ) - Text("Proposals") + Text(stringResource(Res.string.proposals)) } } @@ -426,7 +446,7 @@ fun ChatRoomDetailScreen( modifier = Modifier.width(MaterialTheme.spacing.space125) ) - Text("Invite new member") + Text(stringResource(Res.string.invite_new_member)) } } @@ -503,7 +523,7 @@ fun ChatRoomDetailScreen( modifier = Modifier.width(MaterialTheme.spacing.space125) ) - Text("Leave group") + Text(stringResource(Res.string.leave_group)) } // What it actually does: sets leftGroupAt and posts a line to @@ -544,7 +564,7 @@ fun ChatRoomDetailScreen( modifier = Modifier.width(MaterialTheme.spacing.space125) ) - Text("Delete group") + Text(stringResource(Res.string.delete_group)) } // A soft delete: it sets deletedAt on the local row. Saying @@ -596,13 +616,13 @@ fun ChatRoomDetailScreen( modifier = Modifier.weight(1f) ) Text( - text = "Direct message detail", + text = stringResource(Res.string.direct_message_detail), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) Text( - text = "Direct message functionality will be here.", + text = stringResource(Res.string.direct_message_functionality_will_be_here), style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center ) @@ -663,7 +683,7 @@ private fun ReindexMarmotGroupEventsButton( modifier = Modifier.width(MaterialTheme.spacing.space125) ) - Text("Reindex events") + Text(stringResource(Res.string.reindex_events)) } when (reindexState) { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomMessagingScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomMessagingScreen.kt index e33b746f..aa0ed424 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomMessagingScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomMessagingScreen.kt @@ -58,6 +58,12 @@ import press.mantra.compose.ui.view.state.ChatRoomMessagingUIState import com.vitorpamplona.quartz.nip01Core.core.HexKey import press.mantra.compose.ui.theme.spacing import press.mantra.compose.ui.composable.widgets.Decorative +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +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 @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -280,7 +286,7 @@ fun ChatRoomMessagingScreen( text = if (directMessageRecipient != null) { "Private message to ${chatMessageListViewModel.nameFor(directMessageRecipient.participantPublicKey)}" } else { - "Say what now?" + stringResource(Res.string.say_what_now) } ) }, @@ -380,13 +386,13 @@ fun ChatRoomMessagingScreen( modifier = Modifier.weight(1f) ) Text( - text = "Direct message detail", + text = stringResource(Res.string.direct_message_detail), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) Text( - text = "Direct message functionality will be here.", + text = stringResource(Res.string.direct_message_functionality_will_be_here), style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center ) @@ -413,7 +419,7 @@ fun ChatRoomMessagingScreen( ) Text( - text = "Creating new chat.", + text = stringResource(Res.string.creating_new_chat), style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CreateProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CreateProfileScreen.kt index 4f2102e3..035310fa 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CreateProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CreateProfileScreen.kt @@ -37,6 +37,28 @@ import press.mantra.compose.ui.theme.LocalExtendedColors import press.mantra.compose.ui.view.model.CreateProfileViewModel import press.mantra.compose.ui.view.state.CreateProfileUIState import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.after_this_there_is_no_turning_back +import mantra.composeapp.generated.resources.bio +import mantra.composeapp.generated.resources.create_profile +import mantra.composeapp.generated.resources.end_this +import mantra.composeapp.generated.resources.enter_the_name_you_want_to_use_for_your_2 +import mantra.composeapp.generated.resources.introduce_yourself +import mantra.composeapp.generated.resources.it_is_cryptographical_secure_and +import mantra.composeapp.generated.resources.name +import mantra.composeapp.generated.resources.name_eg_alan_turing +import mantra.composeapp.generated.resources.next +import mantra.composeapp.generated.resources.profile_is_ready +import mantra.composeapp.generated.resources.see_how_deep_the_rabbit_hole_goes +import mantra.composeapp.generated.resources.something_went_wrong_and_we_were_unable_to_2 +import mantra.composeapp.generated.resources.start +import mantra.composeapp.generated.resources.the_above_will_be_your_profile +import mantra.composeapp.generated.resources.this_will_be_shown_when_people_open_your +import mantra.composeapp.generated.resources.this_will_be_the_display_name_for_your +import mantra.composeapp.generated.resources.what_should_people_know_about_you +import mantra.composeapp.generated.resources.you_are_about_to_create_a_nostr_profile +import mantra.composeapp.generated.resources.you_will_be_in_full_control_of_this_profile @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -73,18 +95,18 @@ fun CreateProfileScreen( is CreateProfileUIState.Declaration -> { Text( - "Create profile", + stringResource(Res.string.create_profile), style = MaterialTheme.typography.headlineSmall ) Text( - "You are about to create a NOSTR profile.", + stringResource(Res.string.you_are_about_to_create_a_nostr_profile), style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Center ) Text( - "It is cryptographical secure, and decentralized, putting you in total control of your digital profile", + stringResource(Res.string.it_is_cryptographical_secure_and), style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Center ) @@ -100,13 +122,13 @@ fun CreateProfileScreen( } ) { Text( - text = "Start" + text = stringResource(Res.string.start) ) } } is CreateProfileUIState.InputPrompt -> { Text( - "Create profile", + stringResource(Res.string.create_profile), style = MaterialTheme.typography.headlineSmall ) @@ -134,13 +156,13 @@ fun CreateProfileScreen( ), label = { Text( - text = "Name (eg. Alan Turing)", + text = stringResource(Res.string.name_eg_alan_turing), maxLines = 1, ) }, placeholder = { Text( - text = "Enter the name you want to use for your profile", + text = stringResource(Res.string.enter_the_name_you_want_to_use_for_your_2), maxLines = 1, ) }, @@ -153,7 +175,7 @@ fun CreateProfileScreen( ) Text( - text = "This will be the display name for your profile and also important for search.", + text = stringResource(Res.string.this_will_be_the_display_name_for_your), style = MaterialTheme.typography.labelMedium, textAlign = TextAlign.Center ) @@ -178,14 +200,14 @@ fun CreateProfileScreen( ), label = { Text( - text = "Introduce yourself", + text = stringResource(Res.string.introduce_yourself), maxLines = 1, ) }, placeholder = { Text( - text = "What should people know about you?", + text = stringResource(Res.string.what_should_people_know_about_you), maxLines = 1, ) }, @@ -198,7 +220,7 @@ fun CreateProfileScreen( ) Text( - text = "This will be shown when people open your profile.", + text = stringResource(Res.string.this_will_be_shown_when_people_open_your), style = MaterialTheme.typography.labelMedium, textAlign = TextAlign.Center ) @@ -220,7 +242,7 @@ fun CreateProfileScreen( } ) { Text( - "Next" + stringResource(Res.string.next) ) } @@ -228,7 +250,7 @@ fun CreateProfileScreen( is CreateProfileUIState.ConfirmInput -> { Text( - text = "Name", + text = stringResource(Res.string.name), style = MaterialTheme.typography.labelLarge ) @@ -239,7 +261,7 @@ fun CreateProfileScreen( ) Text( - text = "Bio", + text = stringResource(Res.string.bio), style = MaterialTheme.typography.labelLarge ) @@ -252,7 +274,7 @@ fun CreateProfileScreen( Text( - text = "The above will be your profile.", + text = stringResource(Res.string.the_above_will_be_your_profile), style = MaterialTheme.typography.bodySmall ) @@ -261,7 +283,7 @@ fun CreateProfileScreen( ) Text( - text = "You will be in full control of this profile. If you would like to use it for the long term please remember to backup the profile/keys.", + text = stringResource(Res.string.you_will_be_in_full_control_of_this_profile), style = MaterialTheme.typography.labelSmall, textAlign = TextAlign.Center ) @@ -277,7 +299,7 @@ fun CreateProfileScreen( } ) { Text( - text = "Create profile" + text = stringResource(Res.string.create_profile) ) } } @@ -288,7 +310,7 @@ fun CreateProfileScreen( modifier = Modifier.weight(1f) ) Text( - text = "Something went wrong and we were unable to sign you up. Please try again later.", + text = stringResource(Res.string.something_went_wrong_and_we_were_unable_to_2), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) @@ -308,13 +330,13 @@ fun CreateProfileScreen( modifier = Modifier.weight(1f) ) Text( - text = "Profile is ready", + text = stringResource(Res.string.profile_is_ready), style = MaterialTheme.typography.headlineLarge, textAlign = TextAlign.Center ) Text( - text = "After this there is no turning back." + text = stringResource(Res.string.after_this_there_is_no_turning_back) ) Button( @@ -327,7 +349,7 @@ fun CreateProfileScreen( } ) { Text( - text = "See how deep the rabbit hole goes" + text = stringResource(Res.string.see_how_deep_the_rabbit_hole_goes) ) } @@ -348,7 +370,7 @@ fun CreateProfileScreen( } ) { Text( - text = "end this" + text = stringResource(Res.string.end_this) ) } Spacer( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgApprovalScaffold.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgApprovalScaffold.kt index 30352f13..96ea36e4 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgApprovalScaffold.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgApprovalScaffold.kt @@ -39,6 +39,10 @@ import fr.acinq.phoenix.data.ActiveWallet import kotlinx.coroutines.flow.StateFlow import press.mantra.compose.ui.theme.spacing import press.mantra.compose.ui.composable.widgets.Decorative +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.back +import mantra.composeapp.generated.resources.not_now /** * The frame every ChillDKG approval screen sits in: the view model, the states @@ -149,7 +153,7 @@ internal fun DkgApprovalScaffold( color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center ) - TextButton(onClick = onDone) { Text(text = "Back") } + TextButton(onClick = onDone) { Text(text = stringResource(Res.string.back)) } } return@Scaffold @@ -181,7 +185,7 @@ internal fun DkgApprovalScaffold( onClick = onDone, enabled = !isActionPending ) { - Text(text = "Not now") + Text(text = stringResource(Res.string.not_now)) } Button( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt index 9066e5f2..56d52939 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt @@ -77,6 +77,24 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import press.mantra.compose.ui.theme.spacing import press.mantra.compose.ui.composable.widgets.Decorative +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.create_the_admins_group +import mantra.composeapp.generated.resources.how_many_members_will_it_take_to_sign +import mantra.composeapp.generated.resources.key +import mantra.composeapp.generated.resources.members +import mantra.composeapp.generated.resources.nothing_was_created_and_no_key_exists_it_is +import mantra.composeapp.generated.resources.review_and_confirm +import mantra.composeapp.generated.resources.review_and_contribute +import mantra.composeapp.generated.resources.review_and_join +import mantra.composeapp.generated.resources.shared_key +import mantra.composeapp.generated.resources.start_key_ceremony +import mantra.composeapp.generated.resources.the_ceremony_was_abandoned +import mantra.composeapp.generated.resources.the_group_can_hold_one_key_together_split_so +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 /** * The shared-key ceremony: a ChillDKG ritual run across the group's NIP-17 @@ -169,9 +187,9 @@ fun DkgRitualScreen( Icon(Icons.Default.Key, contentDescription = Decorative) Text( text = when (pending) { - DkgApprovalStep.HOST_KEY -> "Review and join" - DkgApprovalStep.ROUND_1 -> "Review and contribute" - DkgApprovalStep.ROUND_2 -> "Review and confirm" + DkgApprovalStep.HOST_KEY -> stringResource(Res.string.review_and_join) + DkgApprovalStep.ROUND_1 -> stringResource(Res.string.review_and_contribute) + DkgApprovalStep.ROUND_2 -> stringResource(Res.string.review_and_confirm) } ) } @@ -197,7 +215,7 @@ fun DkgRitualScreen( } Text( - text = if (session == null) "Start key ceremony" else "Try again" + text = if (session == null) stringResource(Res.string.start_key_ceremony) else stringResource(Res.string.try_again) ) } }, @@ -222,7 +240,7 @@ fun DkgRitualScreen( ) { if (session == null) { Text( - text = "The group can hold one key together, split so that no single member holds it. Signing with it takes a quorum.", + text = stringResource(Res.string.the_group_can_hold_one_key_together_split_so), style = MaterialTheme.typography.bodyMedium ) @@ -272,7 +290,7 @@ fun DkgRitualScreen( ) { Spacer(modifier = Modifier.weight(1f)) Text( - text = "Shared key", + text = stringResource(Res.string.shared_key), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) @@ -336,12 +354,12 @@ private fun RitualProgress( ) { Icon(Icons.Default.ErrorOutline, contentDescription = Decorative) Column(verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.relatedGap)) { - Text("The ceremony was abandoned.", style = MaterialTheme.typography.titleSmall) + Text(stringResource(Res.string.the_ceremony_was_abandoned), style = MaterialTheme.typography.titleSmall) session.failureReason?.let { Text(it, style = MaterialTheme.typography.bodyMedium) } Text( - "Nothing was created and no key exists. It is safe to run it again.", + stringResource(Res.string.nothing_was_created_and_no_key_exists_it_is), style = MaterialTheme.typography.labelMedium ) } @@ -406,13 +424,13 @@ private fun RitualProgress( verticalAlignment = Alignment.CenterVertically ) { Icon(Icons.Default.CheckCircle, contentDescription = Decorative) - Text("The group has a shared key.", style = MaterialTheme.typography.titleSmall) + Text(stringResource(Res.string.the_group_has_a_shared_key), style = MaterialTheme.typography.titleSmall) } session.thresholdPublicKey?.let { thresholdPublicKey -> val clipboardManager = LocalClipboardManager.current - Text(text = "Key", style = MaterialTheme.typography.labelMedium) + Text(text = stringResource(Res.string.key), style = MaterialTheme.typography.labelMedium) Row( modifier = Modifier.fillMaxWidth(), @@ -445,7 +463,7 @@ private fun RitualProgress( } Text( - text = "Your share of it is on this device only. Your wallet backup restores it — nobody else's share can.", + text = stringResource(Res.string.your_share_of_it_is_on_this_device_only_your), style = MaterialTheme.typography.labelMedium ) @@ -476,7 +494,7 @@ private fun RitualProgress( } else { Icon(Icons.Default.Groups, contentDescription = Decorative) Spacer(modifier = Modifier.width(MaterialTheme.spacing.space100)) - Text(text = "Create the #admins group") + Text(text = stringResource(Res.string.create_the_admins_group)) } } } @@ -506,7 +524,7 @@ private fun RitualRoster( modifier = Modifier.fillMaxWidth().padding(MaterialTheme.spacing.space200), verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space125) ) { - Text(text = "Members", style = MaterialTheme.typography.titleSmall) + Text(text = stringResource(Res.string.members), style = MaterialTheme.typography.titleSmall) members .sortedBy { it.participant.participantPublicKey } @@ -616,7 +634,7 @@ private fun QuorumStepper( verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space50) ) { Text( - text = "How many members will it take to sign?", + text = stringResource(Res.string.how_many_members_will_it_take_to_sign), style = MaterialTheme.typography.titleSmall ) @@ -646,7 +664,7 @@ private fun QuorumStepper( } Text( - text = "This is fixed once the ceremony runs. Changing it later means generating a new key.", + text = stringResource(Res.string.this_is_fixed_once_the_ceremony_runs), style = MaterialTheme.typography.labelMedium ) } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRound1ApprovalScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRound1ApprovalScreen.kt index 18d22550..f8b3babe 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRound1ApprovalScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRound1ApprovalScreen.kt @@ -15,6 +15,10 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey import fr.acinq.phoenix.data.ActiveWallet import kotlinx.coroutines.flow.StateFlow import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.taking_part_with_these_members +import mantra.composeapp.generated.resources.you /** * Asks whether to contribute to the key itself. @@ -69,7 +73,7 @@ fun DkgRound1ApprovalScreen( } Text( - text = "Taking part with these members", + text = stringResource(Res.string.taking_part_with_these_members), style = MaterialTheme.typography.titleSmall ) @@ -82,7 +86,7 @@ fun DkgRound1ApprovalScreen( Text( text = if (key == activeUserPublicKey) { - "You" + stringResource(Res.string.you) } else { member.profile?.humanReadableNameOrPubkey() ?: key.take(12) }, diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRound2ApprovalScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRound2ApprovalScreen.kt index 088cea3c..035c4039 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRound2ApprovalScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRound2ApprovalScreen.kt @@ -15,6 +15,9 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey import fr.acinq.phoenix.data.ActiveWallet import kotlinx.coroutines.flow.StateFlow import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.this_is_the_last_thing_the_ceremony_needs /** * Asks whether to confirm the combined result. @@ -74,7 +77,7 @@ fun DkgRound2ApprovalScreen( } Text( - text = "This is the last thing the ceremony needs from you.", + text = stringResource(Res.string.this_is_the_last_thing_the_ceremony_needs), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/FrostSigningScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/FrostSigningScreen.kt index 1e7e9da2..08d39f6f 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/FrostSigningScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/FrostSigningScreen.kt @@ -58,6 +58,16 @@ import press.mantra.compose.ui.view.model.FrostSigningViewModel import press.mantra.compose.ui.view.state.FrostSigningUIState import press.mantra.compose.ui.theme.spacing import press.mantra.compose.ui.composable.widgets.Decorative +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.don_t_sign +import mantra.composeapp.generated.resources.has_not_taken_part_yet +import mantra.composeapp.generated.resources.members +import mantra.composeapp.generated.resources.read_to_the_end_of_the_list_to_sign +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 /** * One signing session, and the member's decision about it. @@ -105,7 +115,7 @@ fun FrostSigningScreen( TopAppBar( title = { Text( - text = "Sign with the group's key", + text = stringResource(Res.string.sign_with_the_group_s_key), maxLines = 1, overflow = TextOverflow.Ellipsis ) @@ -174,7 +184,7 @@ fun FrostSigningScreen( HorizontalDivider() Text( - text = "Members", + text = stringResource(Res.string.members), style = MaterialTheme.typography.labelMedium ) @@ -224,9 +234,9 @@ fun FrostSigningScreen( supportingContent = { Text( text = when { - member in state.signed -> "Signed their part" - member in state.offeredNonce -> "Ready to sign" - else -> "Has not taken part yet" + member in state.signed -> stringResource(Res.string.signed_their_part) + member in state.offeredNonce -> stringResource(Res.string.ready_to_sign) + else -> stringResource(Res.string.has_not_taken_part_yet) } ) } @@ -260,7 +270,7 @@ fun FrostSigningScreen( if (readable && !seenEverything) { Text( - text = "Read to the end of the list to sign.", + text = stringResource(Res.string.read_to_the_end_of_the_list_to_sign), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -278,7 +288,7 @@ fun FrostSigningScreen( ) { Icon(Icons.Default.Draw, contentDescription = Decorative) Spacer(modifier = Modifier.width(MaterialTheme.spacing.space125)) - Text("Sign") + Text(stringResource(Res.string.sign)) } // Declining stays available whatever the screen could @@ -293,7 +303,7 @@ fun FrostSigningScreen( enabled = !frostSigningViewModel.isActionPending.value, onClick = { frostSigningViewModel.decline(onNavigateBack) } ) { - Text("Don't sign") + Text(stringResource(Res.string.don_t_sign)) } } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/HomeScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/HomeScreen.kt index 2e892a44..e1c08bd1 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/HomeScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/HomeScreen.kt @@ -57,6 +57,11 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import kotlinx.coroutines.launch import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.mantra +import mantra.composeapp.generated.resources.new_chat +import mantra.composeapp.generated.resources.something_went_wrong @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -96,7 +101,7 @@ fun HomeScreen( modifier = Modifier.weight(1f) ) Text( - text = "Something went wrong" + text = stringResource(Res.string.something_went_wrong) ) Spacer( @@ -122,7 +127,7 @@ fun HomeScreen( // the other, and is left alone -- it goes on the wire to relay // operators, so it is a network identity question rather than // a content one. - Text("Mantra") + Text(stringResource(Res.string.mantra)) }, navigationIcon = { IconButton( @@ -168,7 +173,7 @@ fun HomeScreen( ) }, text = { - Text("New chat") + Text(stringResource(Res.string.new_chat)) } ) } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyPackageManagementScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyPackageManagementScreen.kt index 17a0d80a..b3e3f4cd 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyPackageManagementScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyPackageManagementScreen.kt @@ -44,6 +44,12 @@ import press.mantra.compose.ui.view.model.KeyPackageManagementViewModel import press.mantra.compose.ui.view.state.KeyPackageManagementUIState 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.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 @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -72,7 +78,7 @@ fun KeyPackageManagementScreen( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text("Something went wrong") + Text(stringResource(Res.string.something_went_wrong)) } } is KeyPackageManagementUIState.Loaded -> { @@ -81,7 +87,7 @@ fun KeyPackageManagementScreen( TopAppBar( title = { Text( - text = "Key package management" + text = stringResource(Res.string.key_package_management) ) }, navigationIcon = { @@ -123,7 +129,7 @@ fun KeyPackageManagementScreen( ) Text( - text = "Publish new key package" + text = stringResource(Res.string.publish_new_key_package) ) } } @@ -213,7 +219,7 @@ fun KeyPackageManagementScreen( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text("We couldn't find the local profile. Please try again later.") + Text(stringResource(Res.string.we_couldn_t_find_the_local_profile_please)) } } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/LandingScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/LandingScreen.kt index 40b1af2a..7aecc3fa 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/LandingScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/LandingScreen.kt @@ -20,6 +20,15 @@ import androidx.compose.ui.text.style.TextAlign 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.create_profile +import mantra.composeapp.generated.resources.if_you_are_new_to_torch_or_just_want_to +import mantra.composeapp.generated.resources.keep_the_feed_alive +import mantra.composeapp.generated.resources.learn_more +import mantra.composeapp.generated.resources.mantra +import mantra.composeapp.generated.resources.sign_in +import mantra.composeapp.generated.resources.sign_in_to_torch_via_nsec_or_remote_signer @Composable fun LandingScreen( @@ -37,19 +46,19 @@ fun LandingScreen( horizontalAlignment = Alignment.CenterHorizontally ) { Text( - text = "Mantra", + text = stringResource(Res.string.mantra), style = MaterialTheme.typography.headlineLarge ) Text( - text = "Keep the feed alive." + text = stringResource(Res.string.keep_the_feed_alive) ) TextButton( onClick = onNavigateToLearnMore ) { Text( - text = "Learn more", + text = stringResource(Res.string.learn_more), style = MaterialTheme.typography.labelLarge, ) } @@ -63,13 +72,13 @@ fun LandingScreen( ) { Text( - "Sign in", + stringResource(Res.string.sign_in), style = MaterialTheme.typography.bodyLarge ) } Text( - text = "Sign in to Torch via nsec, or remote signer", + text = stringResource(Res.string.sign_in_to_torch_via_nsec_or_remote_signer), modifier = Modifier.padding(MaterialTheme.spacing.space125), style = MaterialTheme.typography.labelSmall, textAlign = TextAlign.Center @@ -83,13 +92,13 @@ fun LandingScreen( onClick = onNavigateToCreateProfile ) { Text( - "Create profile", + stringResource(Res.string.create_profile), style = MaterialTheme.typography.bodyLarge ) } Text( - text = "If you are new to Torch or just want to create a fresh profile", + text = stringResource(Res.string.if_you_are_new_to_torch_or_just_want_to), modifier = Modifier.padding(MaterialTheme.spacing.space125), style = MaterialTheme.typography.labelSmall, textAlign = TextAlign.Center diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrEventDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrEventDetailScreen.kt index 878fe401..269968af 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrEventDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrEventDetailScreen.kt @@ -32,6 +32,12 @@ import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent 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 +import mantra.composeapp.generated.resources.post_functionality_coming_soon +import mantra.composeapp.generated.resources.something_went_wrong +import mantra.composeapp.generated.resources.we_couldn_t_find_your_nostr_event_please_try @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -63,7 +69,7 @@ fun NostrEventDetailScreen( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text("Something went wrong") + Text(stringResource(Res.string.something_went_wrong)) } } is NostrEventDetailUIState.Loaded -> { @@ -98,7 +104,7 @@ fun NostrEventDetailScreen( ) { Text(feedListUIState.localNostrEvent.nostrEvent.content) - Text("post functionality coming soon.") + Text(stringResource(Res.string.post_functionality_coming_soon)) } } else -> { @@ -113,7 +119,7 @@ fun NostrEventDetailScreen( ) { Text(feedListUIState.localNostrEvent.nostrEvent.content) - Text("functionality coming soon.") + Text(stringResource(Res.string.functionality_coming_soon)) } } } @@ -133,7 +139,7 @@ fun NostrEventDetailScreen( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text("We couldn't find your nostr event. Please try again later.") + Text(stringResource(Res.string.we_couldn_t_find_your_nostr_event_please_try)) } } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposalListScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposalListScreen.kt index 32d966f4..aab513c5 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposalListScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposalListScreen.kt @@ -55,6 +55,13 @@ import press.mantra.compose.ui.theme.TorchTheme import press.mantra.compose.ui.view.model.ProposalListViewModel import press.mantra.compose.ui.view.state.ProposalListUIState import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.everything_else +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 /** * Everything the group has asked its shared key to sign. @@ -102,7 +109,7 @@ fun ProposalListScreen( TopAppBar( title = { Text( - text = "Proposals", + text = stringResource(Res.string.proposals), maxLines = 1, overflow = TextOverflow.Ellipsis ) @@ -151,7 +158,7 @@ fun ProposalListScreen( item { Text( modifier = Modifier.fillMaxWidth().padding(top = MaterialTheme.spacing.space600), - text = "This group has not been asked to sign anything yet.", + text = stringResource(Res.string.this_group_has_not_been_asked_to_sign), textAlign = TextAlign.Center ) } @@ -160,7 +167,7 @@ fun ProposalListScreen( if (state.waitingForYou.isNotEmpty()) { item { Text( - text = "Waiting for you", + text = stringResource(Res.string.waiting_for_you), style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.primary ) @@ -181,7 +188,7 @@ fun ProposalListScreen( if (state.waitingForYou.isNotEmpty()) { item { Text( - text = "Everything else", + text = stringResource(Res.string.everything_else), style = MaterialTheme.typography.labelMedium ) } @@ -296,7 +303,7 @@ private fun ProposalCard( // it saying what it said there. if (proposal.awaitsYou) { Text( - text = "Review", + text = stringResource(Res.string.review), style = MaterialTheme.typography.labelLarge, color = cardContentColor ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchMemberToAddToChatRoomScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchMemberToAddToChatRoomScreen.kt index 668879fc..1fbcfcbd 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchMemberToAddToChatRoomScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchMemberToAddToChatRoomScreen.kt @@ -41,6 +41,11 @@ import press.mantra.compose.ui.view.model.SearchMemberToAddToChatRoomViewModel import press.mantra.compose.ui.view.state.SearchMemberToAddToChatRoomUIState 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.currently_no_contacts_please_search_and_chat +import mantra.composeapp.generated.resources.search_member_functionality +import mantra.composeapp.generated.resources.search_message_functionality_will_be_here @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -110,7 +115,7 @@ fun SearchMemberToAddToChatRoomScreen( ) Text( modifier = Modifier.padding(MaterialTheme.spacing.space250), - text = "Currently no contacts. Please search and chat with a few people.", + text = stringResource(Res.string.currently_no_contacts_please_search_and_chat), textAlign = TextAlign.Center ) @@ -182,13 +187,13 @@ fun SearchMemberToAddToChatRoomScreen( modifier = Modifier.weight(1f) ) Text( - text = "Search member functionality", + text = stringResource(Res.string.search_member_functionality), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) Text( - text = "Search message functionality will be here.", + text = stringResource(Res.string.search_message_functionality_will_be_here), style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchResultScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchResultScreen.kt index ca4ca0f6..7a096b57 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchResultScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchResultScreen.kt @@ -55,6 +55,12 @@ import press.mantra.compose.ui.view.model.SearchResultViewModel import press.mantra.compose.ui.view.state.SearchResultListUIState import kotlinx.coroutines.launch import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +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 @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -109,7 +115,7 @@ fun SearchResultScreen( enabled = false, expanded = expanded, onExpandedChange = { expanded = it }, - placeholder = { Text("Search") }, + placeholder = { Text(stringResource(Res.string.search)) }, leadingIcon = { if (expanded) { IconButton( @@ -200,7 +206,7 @@ fun SearchResultScreen( ) { when(SearchResultType.entries[pageIndex]) { SearchResultType.Profiles -> { - Text("Profiles") + Text(stringResource(Res.string.profiles)) } } @@ -214,7 +220,7 @@ fun SearchResultScreen( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "Something went wrong", + text = stringResource(Res.string.something_went_wrong), ) } } @@ -229,7 +235,7 @@ fun SearchResultScreen( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "No events were found", + text = stringResource(Res.string.no_events_were_found), ) } } else { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchScreen.kt index 08ca2a73..93afe8c1 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchScreen.kt @@ -63,6 +63,13 @@ import press.mantra.compose.ui.theme.TorchTheme import press.mantra.compose.ui.view.model.SearchViewModel import press.mantra.compose.ui.view.state.SearchUIState import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.profiles +import mantra.composeapp.generated.resources.recents +import mantra.composeapp.generated.resources.search +import mantra.composeapp.generated.resources.search_hashtags +import mantra.composeapp.generated.resources.trending_notes_functionality_coming_soon_in @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -117,7 +124,7 @@ fun SearchScreen( }, expanded = expanded, onExpandedChange = { expanded = it }, - placeholder = { Text("Search") }, + placeholder = { Text(stringResource(Res.string.search)) }, leadingIcon = { if (expanded) { IconButton( @@ -156,7 +163,7 @@ fun SearchScreen( }, supportingContent = { Text( - text = "Search" + text = stringResource(Res.string.search) ) }, leadingContent = { @@ -196,7 +203,7 @@ fun SearchScreen( headlineContent = { Text(textFieldState.text.toString()) }, supportingContent = { Text( - text = "Search hashtags" + text = stringResource(Res.string.search_hashtags) ) }, leadingContent = { @@ -289,7 +296,7 @@ fun SearchScreen( modifier = Modifier.padding(MaterialTheme.spacing.space125), ) { Text( - text = "Profiles", + text = stringResource(Res.string.profiles), style = MaterialTheme.typography.titleMedium ) } @@ -344,7 +351,7 @@ fun SearchScreen( modifier = Modifier.padding(MaterialTheme.spacing.space125), ) { Text( - text = "Recents", + text = stringResource(Res.string.recents), style = MaterialTheme.typography.titleMedium ) } @@ -404,7 +411,7 @@ fun SearchScreen( title = {} ) Text( - text = "Trending notes functionality coming soon. In the meantime search for what you are looking for.", + text = stringResource(Res.string.trending_notes_functionality_coming_soon_in), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomMembersScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomMembersScreen.kt index f4468b9c..e115d788 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomMembersScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomMembersScreen.kt @@ -43,6 +43,13 @@ import press.mantra.compose.ui.view.model.SelectChatRoomMembersViewModel import press.mantra.compose.ui.view.state.SelectChatRoomMembersUIState 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.next +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 /** * Second step of group creation: pick who is in the group. @@ -120,7 +127,7 @@ fun SelectChatRoomMembersScreen( text = if (selectedCount > 0) { "Next with $selectedCount" } else { - "Next" + stringResource(Res.string.next) } ) } @@ -131,7 +138,7 @@ fun SelectChatRoomMembersScreen( text = if (selectedCount > 0) { "$selectedCount selected" } else { - "No one selected yet" + stringResource(Res.string.no_one_selected_yet) }, style = MaterialTheme.typography.labelLarge ) @@ -153,19 +160,19 @@ fun SelectChatRoomMembersScreen( ) Text( - text = "No one to add yet.", + text = stringResource(Res.string.no_one_to_add_yet), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) Text( - text = "Search for people and chat with them first — everyone you know locally shows up here.", + text = stringResource(Res.string.search_for_people_and_chat_with_them_first), style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center ) Text( - text = "You can still carry on and invite people later.", + text = stringResource(Res.string.you_can_still_carry_on_and_invite_people), style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomTypeScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomTypeScreen.kt index 9a03ffae..3831e0c8 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomTypeScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomTypeScreen.kt @@ -57,6 +57,14 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import press.mantra.compose.ui.theme.spacing import press.mantra.compose.ui.composable.widgets.Decorative +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.create_chat +import mantra.composeapp.generated.resources.how_many_admins_have_to_approve_a_change +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 /** * Last step of group creation: convenient (one admin) or robust (everyone @@ -149,9 +157,9 @@ fun SelectChatRoomTypeScreen( Text( text = if (selectChatRoomTypeViewModel.createdChatRoomId.value != null) { - "Open chat" + stringResource(Res.string.open_chat) } else { - "Create chat" + stringResource(Res.string.create_chat) } ) } @@ -160,8 +168,8 @@ fun SelectChatRoomTypeScreen( Text( modifier = Modifier.padding(start = MaterialTheme.spacing.space200), text = when (selectChatRoomTypeUIState.members.size) { - 0 -> "Just you for now" - 1 -> "You and 1 other" + 0 -> stringResource(Res.string.just_you_for_now) + 1 -> stringResource(Res.string.you_and_1_other) else -> "You and ${selectChatRoomTypeUIState.members.size} others" }, style = MaterialTheme.typography.labelLarge @@ -211,7 +219,7 @@ fun SelectChatRoomTypeScreen( } Text( - text = "This decides who can change the group later. You can't switch afterwards.", + text = stringResource(Res.string.this_decides_who_can_change_the_group_later), style = MaterialTheme.typography.labelMedium, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth().padding(horizontal = MaterialTheme.spacing.space125) @@ -400,7 +408,7 @@ private fun QuorumPicker( verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space50) ) { Text( - text = "How many admins have to approve a change?", + text = stringResource(Res.string.how_many_admins_have_to_approve_a_change), style = MaterialTheme.typography.titleSmall ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ShareProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ShareProfileScreen.kt index 012eeb7d..b596258a 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ShareProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ShareProfileScreen.kt @@ -45,6 +45,12 @@ import press.mantra.compose.ui.view.state.ShareProfileUIState import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.profile +import mantra.composeapp.generated.resources.re_broadcast +import mantra.composeapp.generated.resources.something_went_wrong +import mantra.composeapp.generated.resources.we_couldn_t_find_the_local_profile_please @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -73,7 +79,7 @@ fun ShareProfileScreen( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text("Something went wrong") + Text(stringResource(Res.string.something_went_wrong)) } } is ShareProfileUIState.Loaded -> { @@ -82,7 +88,7 @@ fun ShareProfileScreen( TopAppBar( title = { Text( - text = "Profile" + text = stringResource(Res.string.profile) ) }, navigationIcon = { @@ -192,7 +198,7 @@ fun ShareProfileScreen( } ) { Text( - "Re-broadcast" + stringResource(Res.string.re_broadcast) ) } } @@ -212,7 +218,7 @@ fun ShareProfileScreen( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text("We couldn't find the local profile. Please try again later.") + Text(stringResource(Res.string.we_couldn_t_find_the_local_profile_please)) } } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SignInScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SignInScreen.kt index 1cbeda64..534e803c 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SignInScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SignInScreen.kt @@ -32,6 +32,18 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.quartz.nip19Bech32.entities.NPub import com.vitorpamplona.quartz.nip19Bech32.entities.NSec import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.be_sure_to_keep_this_nsec_safe +import mantra.composeapp.generated.resources.enter_the_nsec_or_npub_read_only_that_you +import mantra.composeapp.generated.resources.next +import mantra.composeapp.generated.resources.nsec_npub_nip_05_static_address +import mantra.composeapp.generated.resources.sign_in +import mantra.composeapp.generated.resources.sign_in_to_nsec +import mantra.composeapp.generated.resources.sign_in_with_an_npub +import mantra.composeapp.generated.resources.something_went_wrong_and_we_were_unable_to +import mantra.composeapp.generated.resources.this_will_give_you_read_only_access_to_the +import mantra.composeapp.generated.resources.this_will_give_you_write_access_to_the @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -63,7 +75,7 @@ fun SignInToProfileScreen( when (val signInToProfileUIState = signInToProfileViewModel.signInToProfileUIState.value) { is press.mantra.compose.ui.view.state.SignInToProfileUIState.InputPrompt -> { Text( - "Sign in", + stringResource(Res.string.sign_in), style = MaterialTheme.typography.headlineSmall ) @@ -87,13 +99,13 @@ fun SignInToProfileScreen( ), label = { Text( - text = "nsec, npub, nip-05 (static address)", + text = stringResource(Res.string.nsec_npub_nip_05_static_address), maxLines = 1, ) }, placeholder = { Text( - text = "Enter the nsec or npub (read only) that you want to sign in as", + text = stringResource(Res.string.enter_the_nsec_or_npub_read_only_that_you), maxLines = 1, ) }, @@ -129,7 +141,7 @@ fun SignInToProfileScreen( }, ) { Text( - "Next" + stringResource(Res.string.next) ) Icon( Icons.Default.NavigateNext, @@ -144,7 +156,7 @@ fun SignInToProfileScreen( modifier = Modifier.weight(1f) ) Text( - text = "Sign in with an npub", + text = stringResource(Res.string.sign_in_with_an_npub), style = MaterialTheme.typography.headlineMedium ) @@ -168,7 +180,7 @@ fun SignInToProfileScreen( Text( - text = "This will give you read only access to the profile.", + text = stringResource(Res.string.this_will_give_you_read_only_access_to_the), style = MaterialTheme.typography.labelSmall, textAlign = TextAlign.Center ) @@ -184,7 +196,7 @@ fun SignInToProfileScreen( } ) { Text( - text = "Sign in" + text = stringResource(Res.string.sign_in) ) } } @@ -196,12 +208,12 @@ fun SignInToProfileScreen( modifier = Modifier.weight(1f) ) Text( - text = "Sign in to nsec", + text = stringResource(Res.string.sign_in_to_nsec), style = MaterialTheme.typography.headlineMedium ) Text( - text = "Be sure to keep this nsec safe.", + text = stringResource(Res.string.be_sure_to_keep_this_nsec_safe), textAlign = TextAlign.Center, style = MaterialTheme.typography.bodyMedium ) @@ -214,7 +226,7 @@ fun SignInToProfileScreen( Text( - text = "This will give you write access to the profile.", + text = stringResource(Res.string.this_will_give_you_write_access_to_the), style = MaterialTheme.typography.labelSmall, textAlign = TextAlign.Center ) @@ -230,7 +242,7 @@ fun SignInToProfileScreen( } ) { Text( - text = "Sign in" + text = stringResource(Res.string.sign_in) ) } } @@ -241,7 +253,7 @@ fun SignInToProfileScreen( modifier = Modifier.weight(1f) ) Text( - text = "Something went wrong and we were unable to sign in to your provided profile. Please try again later.", + text = stringResource(Res.string.something_went_wrong_and_we_were_unable_to), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SocialPreconditionScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SocialPreconditionScreen.kt index a4d20fb4..6f2f75a1 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SocialPreconditionScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SocialPreconditionScreen.kt @@ -23,6 +23,14 @@ import androidx.compose.ui.text.style.TextAlign 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.invite_a_friend +import mantra.composeapp.generated.resources.skip_for_now +import mantra.composeapp.generated.resources.tell_friends_to_join_you_so_your_feed_stays +import mantra.composeapp.generated.resources.view_and_accept_invites_you_may_have +import mantra.composeapp.generated.resources.view_invites +import mantra.composeapp.generated.resources.who_will_you_be_passing_the_aux_to @Composable fun SocialPreconditionScreen( @@ -47,7 +55,7 @@ fun SocialPreconditionScreen( ) }, text = { - Text("Skip for now") + Text(stringResource(Res.string.skip_for_now)) }, onClick = { onNavigateToSkipForNow.invoke() @@ -67,7 +75,7 @@ fun SocialPreconditionScreen( ) { Text( - "Who will you be passing the aux to?", + stringResource(Res.string.who_will_you_be_passing_the_aux_to), textAlign = TextAlign.Center, style = MaterialTheme.typography.titleLarge ) @@ -77,7 +85,7 @@ fun SocialPreconditionScreen( ) Text( - "Tell friends to join you so your feed stays lively and fresh.", + stringResource(Res.string.tell_friends_to_join_you_so_your_feed_stays), textAlign = TextAlign.Center ) @@ -87,7 +95,7 @@ fun SocialPreconditionScreen( } ) { Text( - "Invite a friend" + stringResource(Res.string.invite_a_friend) ) } @@ -96,7 +104,7 @@ fun SocialPreconditionScreen( ) Text( - "View and accept invites you may have received to stay connected with others.", + stringResource(Res.string.view_and_accept_invites_you_may_have), textAlign = TextAlign.Center ) @@ -105,7 +113,7 @@ fun SocialPreconditionScreen( onNavigateToViewInvites.invoke() } ) { - Text("View invites") + Text(stringResource(Res.string.view_invites)) } } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SovereignWalletStartupScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SovereignWalletStartupScreen.kt index 3dd8a638..02d664d9 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SovereignWalletStartupScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SovereignWalletStartupScreen.kt @@ -27,6 +27,11 @@ import fr.acinq.phoenix.utils.preferences.UserWalletMetadata import fr.acinq.phoenix.utils.preferences.getByWalletIdOrDefault import kotlinx.coroutines.flow.first import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.lock_prompt_coming_soon +import mantra.composeapp.generated.resources.select_a_wallet +import mantra.composeapp.generated.resources.startup_error @Composable fun SovereignWalletStartupScreen( @@ -135,7 +140,7 @@ fun SovereignWalletStartupScreen( topContent = { Spacer(Modifier.height(MaterialTheme.spacing.space800)) Text( - text = "Select a wallet", + text = stringResource(Res.string.select_a_wallet), style = MaterialTheme.typography.headlineSmall ) Spacer(Modifier.height(MaterialTheme.spacing.space200)) @@ -191,7 +196,7 @@ fun SovereignWalletStartupScreen( Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( - text = "Startup error" + text = stringResource(Res.string.startup_error) ) when (startupState) { @@ -280,7 +285,7 @@ private fun BoxScope.ScreenLockPrompt( goToWalletSelector: (() -> Unit)?, ) { Text( - text = "Lock prompt coming soon.", + text = stringResource(Res.string.lock_prompt_coming_soon), ) // val scope = rememberCoroutineScope() // diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslateChunkScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslateChunkScreen.kt index 15826db4..5d05cf60 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslateChunkScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslateChunkScreen.kt @@ -54,6 +54,14 @@ import press.mantra.compose.ui.theme.TorchTheme import press.mantra.compose.ui.view.model.TranslateChunkViewModel import press.mantra.compose.ui.view.state.TranslateChunkUIState import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.enter_the_translation_for_this_chunk +import mantra.composeapp.generated.resources.original +import mantra.composeapp.generated.resources.propose_translation +import mantra.composeapp.generated.resources.translate_chunk +import mantra.composeapp.generated.resources.translated_text +import mantra.composeapp.generated.resources.translation @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -109,7 +117,7 @@ fun TranslateChunkScreen( modifier = Modifier.imePadding(), topBar = { TopAppBar( - title = { Text("Translate chunk") }, + title = { Text(stringResource(Res.string.translate_chunk)) }, navigationIcon = { IconButton(onClick = onNavigateBack) { Icon( @@ -177,7 +185,7 @@ fun TranslateChunkScreen( Icons.Default.Add, contentDescription = "Propose translation" ) - Text("Propose translation") + Text(stringResource(Res.string.propose_translation)) } } ) @@ -201,7 +209,7 @@ fun TranslateChunkScreen( } Text( - text = "Original", + text = stringResource(Res.string.original), style = MaterialTheme.typography.labelMedium ) Card { @@ -213,14 +221,14 @@ fun TranslateChunkScreen( } Text( - text = "Translation", + text = stringResource(Res.string.translation), style = MaterialTheme.typography.labelMedium ) OutlinedTextField( modifier = Modifier.fillMaxWidth(), state = translationFieldState, - label = { Text("Translated text") }, - placeholder = { Text("Enter the translation for this chunk") }, + label = { Text(stringResource(Res.string.translated_text)) }, + placeholder = { Text(stringResource(Res.string.enter_the_translation_for_this_chunk)) }, ) } } @@ -234,7 +242,7 @@ fun TranslateChunkScreen( ) { Spacer(modifier = Modifier.weight(1f)) Text( - text = "Translate chunk", + text = stringResource(Res.string.translate_chunk), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationArtifactVersionDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationArtifactVersionDetailScreen.kt index 7d72bf70..9dacc18e 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationArtifactVersionDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationArtifactVersionDetailScreen.kt @@ -47,6 +47,14 @@ import press.mantra.compose.ui.theme.TorchTheme import press.mantra.compose.ui.view.model.TranslationArtifactVersionDetailViewModel import press.mantra.compose.ui.view.state.TranslationArtifactVersionDetailUIState import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.chapters +import mantra.composeapp.generated.resources.details +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 @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -134,7 +142,7 @@ fun TranslationArtifactVersionDetailScreen( supportingContent = { Text( if (!translationDetailUIState.canSign) { - "This group has no shared key, so it cannot sign them in." + stringResource(Res.string.this_group_has_no_shared_key_so_it_cannot) } else { "They were signed into the artifact after this " + "translation. Ask the group to add them so they " + @@ -166,7 +174,7 @@ fun TranslationArtifactVersionDetailScreen( ) } ) { - Text("Propose") + Text(stringResource(Res.string.propose)) } } ) @@ -179,12 +187,12 @@ fun TranslationArtifactVersionDetailScreen( // Chapters with translation progress item { Text( - text = "Chapters", + text = stringResource(Res.string.chapters), style = MaterialTheme.typography.labelMedium ) } if (translationDetailUIState.chapters.isEmpty()) { - item { Text("No chapters.") } + item { Text(stringResource(Res.string.no_chapters)) } } else { items( items = translationDetailUIState.chapters, @@ -223,7 +231,7 @@ fun TranslationArtifactVersionDetailScreen( // Details item { Text( - text = "Details", + text = stringResource(Res.string.details), style = MaterialTheme.typography.labelMedium ) } @@ -250,7 +258,7 @@ fun TranslationArtifactVersionDetailScreen( ) { Spacer(modifier = Modifier.weight(1f)) Text( - text = "Translation detail", + text = stringResource(Res.string.translation_detail), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationChapterScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationChapterScreen.kt index 40eede45..6f74e5e7 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationChapterScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationChapterScreen.kt @@ -48,6 +48,10 @@ import press.mantra.compose.ui.view.model.TranslationChapterViewModel import press.mantra.compose.ui.view.state.ChunkTranslationPair import press.mantra.compose.ui.view.state.TranslationChapterUIState import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.chapter_translation +import mantra.composeapp.generated.resources.this_chapter_has_no_chunks @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -87,7 +91,7 @@ fun TranslationChapterScreen( Scaffold( topBar = { TopAppBar( - title = { Text("Chapter translation") }, + title = { Text(stringResource(Res.string.chapter_translation)) }, navigationIcon = { IconButton(onClick = onNavigateBack) { Icon( @@ -127,7 +131,7 @@ fun TranslationChapterScreen( modifier = Modifier.fillMaxWidth().padding(MaterialTheme.spacing.containerPadding), contentAlignment = Alignment.Center ) { - Text("This chapter has no chunks.") + Text(stringResource(Res.string.this_chapter_has_no_chunks)) } } } else { @@ -164,7 +168,7 @@ fun TranslationChapterScreen( ) { Spacer(modifier = Modifier.weight(1f)) Text( - text = "Chapter translation", + text = stringResource(Res.string.chapter_translation), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnannouncedProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnannouncedProfileScreen.kt index d267f811..408d55bd 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnannouncedProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnannouncedProfileScreen.kt @@ -18,6 +18,10 @@ import androidx.compose.ui.text.style.TextAlign 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.everything_is_cryptographical_sound_just +import mantra.composeapp.generated.resources.torch_will_be_broadcast_what_you_publish_to @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -39,13 +43,13 @@ fun UnannouncedProfileScreen() { modifier = Modifier.weight(1f) ) Text( - text = "Everything is cryptographical sound. Just announcing your profile to the world.", + text = stringResource(Res.string.everything_is_cryptographical_sound_just), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) Text( - text = "Torch will be broadcast what you publish to a distributed set of relays so that it's decentralized.", + text = stringResource(Res.string.torch_will_be_broadcast_what_you_publish_to), style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnindexedProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnindexedProfileScreen.kt index eecf6676..ef3d18cf 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnindexedProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnindexedProfileScreen.kt @@ -18,6 +18,10 @@ import androidx.compose.ui.text.style.TextAlign 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.events_are_indexed_so_that_we_can_deliver_a +import mantra.composeapp.generated.resources.everything_is_cryptographical_sound_just_2 @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -39,13 +43,13 @@ fun UnindexedProfileScreen() { modifier = Modifier.weight(1f) ) Text( - text = "Everything is cryptographical sound. Just indexing your profile on the device.", + text = stringResource(Res.string.everything_is_cryptographical_sound_just_2), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) Text( - text = "Events are indexed so that we can deliver a premium local first experience.", + text = stringResource(Res.string.events_are_indexed_so_that_we_can_deliver_a), style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileScreen.kt index 6222d9a5..92e99bb6 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileScreen.kt @@ -18,6 +18,10 @@ import androidx.compose.ui.text.style.TextAlign 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.all_broadcasts_are_queued_so_that_we_can +import mantra.composeapp.generated.resources.everything_is_cryptographical_sound_just_3 @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -39,13 +43,13 @@ fun UnqueuedProfileScreen() { modifier = Modifier.weight(1f) ) Text( - text = "Everything is cryptographical sound. Just need to queue your profile and announce it to the world.", + text = stringResource(Res.string.everything_is_cryptographical_sound_just_3), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) Text( - text = "All broadcasts are queued so that we can manage data usage on metered connections.", + text = stringResource(Res.string.all_broadcasts_are_queued_so_that_we_can), style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileSynchronizationScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileSynchronizationScreen.kt index 56504446..832ae60b 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileSynchronizationScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileSynchronizationScreen.kt @@ -20,6 +20,10 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.we_are_looking_for_your_profile_on_as_many +import mantra.composeapp.generated.resources.we_are_searching_the_internet_to_find_your @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -53,13 +57,13 @@ fun UnqueuedProfileSynchronizationScreen( modifier = Modifier.weight(1f) ) Text( - text = "We are searching the internet to find your profile and complete sign in.", + text = stringResource(Res.string.we_are_searching_the_internet_to_find_your), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) Text( - text = "We are looking for your profile on as many relays as possible. Nostr aims to be decentralized by distributing data to multiple nodse.", + text = stringResource(Res.string.we_are_looking_for_your_profile_on_as_many), style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsignedProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsignedProfileScreen.kt index a9d58335..a25a79d4 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsignedProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsignedProfileScreen.kt @@ -18,6 +18,10 @@ import androidx.compose.ui.text.style.TextAlign 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.as_long_as_you_control_your_keys_there_can +import mantra.composeapp.generated.resources.your_profile_is_almost_ready_just_getting_it @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -39,13 +43,13 @@ fun UnsignedProfileScreen() { modifier = Modifier.weight(1f) ) Text( - text = "Your profile is almost ready... just getting it's first cryptographic signature together.", + text = stringResource(Res.string.your_profile_is_almost_ready_just_getting_it), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) Text( - text = "As long as you control your keys there can be no dispute about who YOU actually is.", + text = stringResource(Res.string.as_long_as_you_control_your_keys_there_can), style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsyncedProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsyncedProfileScreen.kt index 7ff41c7d..2ad93668 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsyncedProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsyncedProfileScreen.kt @@ -18,6 +18,10 @@ import androidx.compose.ui.text.style.TextAlign 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.we_are_looking_for_your_profile_on_as_many +import mantra.composeapp.generated.resources.we_are_searching_the_internet_to_find_your @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -41,13 +45,13 @@ fun UnsyncedProfileScreen( modifier = Modifier.weight(1f) ) Text( - text = "We are searching the internet to find your profile and complete sign in.", + text = stringResource(Res.string.we_are_searching_the_internet_to_find_your), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) Text( - text = "We are looking for your profile on as many relays as possible. Nostr aims to be decentralized by distributing data to multiple nodse.", + text = stringResource(Res.string.we_are_looking_for_your_profile_on_as_many), style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/WriteNewNoteScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/WriteNewNoteScreen.kt index cfb5a817..8a6469b4 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/WriteNewNoteScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/WriteNewNoteScreen.kt @@ -45,6 +45,16 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import kotlin.time.Clock import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.next +import mantra.composeapp.generated.resources.something_went_wrong_and_we_were_unable_to_3 +import mantra.composeapp.generated.resources.the_above_will_be_your_new_note +import mantra.composeapp.generated.resources.transmit_note +import mantra.composeapp.generated.resources.type_out_what_you_would_like_to_publish +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 @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalFoundationApi::class) @Composable @@ -148,18 +158,18 @@ fun WriteNewNoteScreen( label = { Text( text = if (writeNewNoteUIState.inReplyToNostrEvent != null) { - "What's your reply to the above" + stringResource(Res.string.what_s_your_reply_to_the_above) } else if (writeNewNoteUIState.quotedNostrEvent != null) { - "What's your comment on the below" + stringResource(Res.string.what_s_your_comment_on_the_below) } else { - "What vibrations do you want to send out?" + stringResource(Res.string.what_vibrations_do_you_want_to_send_out) }, maxLines = 1, ) }, placeholder = { Text( - text = "Type out what you would like to publish", + text = stringResource(Res.string.type_out_what_you_would_like_to_publish), maxLines = 1, ) }, @@ -271,7 +281,7 @@ fun WriteNewNoteScreen( }, ) { Text( - "Next" + stringResource(Res.string.next) ) Icon( Icons.Default.NavigateNext, @@ -399,7 +409,7 @@ fun WriteNewNoteScreen( } Text( - text = "The above will be your new note.", + text = stringResource(Res.string.the_above_will_be_your_new_note), style = MaterialTheme.typography.bodySmall ) @@ -419,7 +429,7 @@ fun WriteNewNoteScreen( } ) { Text( - text = "Transmit note" + text = stringResource(Res.string.transmit_note) ) } } @@ -430,7 +440,7 @@ fun WriteNewNoteScreen( modifier = Modifier.weight(1f) ) Text( - text = "Something went wrong and we were unable to write a new note. Please try again later.", + text = stringResource(Res.string.something_went_wrong_and_we_were_unable_to_3), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/ArticleCard.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/ArticleCard.kt index 89cbf02f..ae095edf 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/ArticleCard.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/ArticleCard.kt @@ -22,6 +22,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow 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.article +import mantra.composeapp.generated.resources.loading_article +import mantra.composeapp.generated.resources.untitled_article @Composable private fun ArticleCard( @@ -71,7 +76,7 @@ private fun ArticleCard( ) Spacer(Modifier.width(MaterialTheme.spacing.space100)) Text( - text = "Loading article...", + text = stringResource(Res.string.loading_article), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -99,14 +104,14 @@ private fun ArticleCard( modifier = Modifier.padding(end = MaterialTheme.spacing.compactPadding) ) { Text( - text = "ARTICLE", + text = stringResource(Res.string.article), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onPrimaryContainer, modifier = Modifier.padding(horizontal = MaterialTheme.spacing.space75, vertical = MaterialTheme.spacing.space25) ) } Text( - text = title ?: "Untitled article", + text = title ?: stringResource(Res.string.untitled_article), style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.onSurface, maxLines = 2, diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/ImageWithContextMenu.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/ImageWithContextMenu.kt index 7423711c..6cb28994 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/ImageWithContextMenu.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/ImageWithContextMenu.kt @@ -33,6 +33,11 @@ import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.text.AnnotatedString 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.copy_url +import mantra.composeapp.generated.resources.download +import mantra.composeapp.generated.resources.tap_to_load @OptIn(ExperimentalFoundationApi::class) @Composable @@ -67,7 +72,7 @@ internal fun ImageWithContextMenu(meta: MediaMeta, onFullScreen: () -> Unit) { ) Spacer(Modifier.height(MaterialTheme.spacing.space100)) Text( - "Tap to load", + stringResource(Res.string.tap_to_load), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -95,14 +100,14 @@ internal fun ImageWithContextMenu(meta: MediaMeta, onFullScreen: () -> Unit) { onDismissRequest = { showMenu = false } ) { DropdownMenuItem( - text = { Text("Copy URL") }, + text = { Text(stringResource(Res.string.copy_url)) }, onClick = { clipboardManager.setText(AnnotatedString(url)) showMenu = false } ) DropdownMenuItem( - text = { Text("Download") }, + text = { Text(stringResource(Res.string.download)) }, onClick = { showMenu = false // scope.launch { MediaDownloader.downloadMedia(context, url) } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/LightningInvoiceCard.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/LightningInvoiceCard.kt index 83739e03..0f76c3d6 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/LightningInvoiceCard.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/LightningInvoiceCard.kt @@ -35,6 +35,16 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import press.mantra.compose.ui.theme.spacing import press.mantra.compose.ui.composable.widgets.Decorative +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.any_amount +import mantra.composeapp.generated.resources.cancel +import mantra.composeapp.generated.resources.expired +import mantra.composeapp.generated.resources.failed +import mantra.composeapp.generated.resources.lightning_invoice +import mantra.composeapp.generated.resources.paid +import mantra.composeapp.generated.resources.pay +import mantra.composeapp.generated.resources.pay_now @Composable internal fun LightningInvoiceCard( @@ -52,7 +62,7 @@ internal fun LightningInvoiceCard( if (showConfirm) { AlertDialog( onDismissRequest = { showConfirm = false }, - title = { Text("Lightning invoice") }, + title = { Text(stringResource(Res.string.lightning_invoice)) }, text = { Column { Text( @@ -86,11 +96,11 @@ internal fun LightningInvoiceCard( containerColor = primary, contentColor = onPrimary ) - ) { Text("Pay") } + ) { Text(stringResource(Res.string.pay)) } }, dismissButton = { TextButton(onClick = { showConfirm = false }) { - Text("Cancel") + Text(stringResource(Res.string.cancel)) } } ) @@ -119,7 +129,7 @@ internal fun LightningInvoiceCard( Text( text = if (decoded.amount != null) "${decoded.amount!!.truncateToSatoshi()}" - else "Any amount", + else stringResource(Res.string.any_amount), style = MaterialTheme.typography.titleSmall, color = primary ) @@ -133,7 +143,7 @@ internal fun LightningInvoiceCard( } if (isExpired) { Text( - text = "Expired", + text = stringResource(Res.string.expired), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.error ) @@ -148,12 +158,12 @@ internal fun LightningInvoiceCard( strokeWidth = 2.dp ) InvoicePayState.Success -> Text( - text = "✓ Paid", + text = stringResource(Res.string.paid), style = MaterialTheme.typography.labelMedium, color = primary ) InvoicePayState.Failed -> Text( - text = "✗ Failed", + text = stringResource(Res.string.failed), style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.error ) @@ -166,8 +176,8 @@ internal fun LightningInvoiceCard( ) ) { Text( - if (isExpired) "Expired" - else "Pay now" + if (isExpired) stringResource(Res.string.expired) + else stringResource(Res.string.pay_now) ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/LiveStreamCardContent.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/LiveStreamCardContent.kt index af8f3235..6b4f135f 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/LiveStreamCardContent.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/LiveStreamCardContent.kt @@ -20,6 +20,12 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow 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.ended +import mantra.composeapp.generated.resources.live +import mantra.composeapp.generated.resources.live_stream +import mantra.composeapp.generated.resources.loading_stream @Composable internal fun LiveStreamCardContent( @@ -45,7 +51,7 @@ internal fun LiveStreamCardContent( ) Spacer(Modifier.width(MaterialTheme.spacing.space100)) Text( - text = "Loading stream...", + text = stringResource(Res.string.loading_stream), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -92,7 +98,7 @@ internal fun LiveStreamCardContent( modifier = Modifier.padding(end = MaterialTheme.spacing.compactPadding) ) { Text( - text = "LIVE", + text = stringResource(Res.string.live), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onError, modifier = Modifier.padding(horizontal = MaterialTheme.spacing.space75, vertical = MaterialTheme.spacing.space25) @@ -105,7 +111,7 @@ internal fun LiveStreamCardContent( modifier = Modifier.padding(end = MaterialTheme.spacing.compactPadding) ) { Text( - text = "ENDED", + text = stringResource(Res.string.ended), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(horizontal = MaterialTheme.spacing.space75, vertical = MaterialTheme.spacing.space25) @@ -113,7 +119,7 @@ internal fun LiveStreamCardContent( } } Text( - text = title ?: "Live stream", + text = title ?: stringResource(Res.string.live_stream), style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.onSurface, maxLines = 2, diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/QuotedAddressableNote.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/QuotedAddressableNote.kt index d0c16d3c..5bbafff5 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/QuotedAddressableNote.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/QuotedAddressableNote.kt @@ -18,6 +18,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.TextStyle 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.loading_note @Composable @@ -64,7 +67,7 @@ internal fun QuotedAddressableNote( ) Spacer(Modifier.width(MaterialTheme.spacing.space100)) Text( - text = "Loading note...", + text = stringResource(Res.string.loading_note), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/QuotedNote.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/QuotedNote.kt index 39eef02b..efc96562 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/QuotedNote.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/QuotedNote.kt @@ -21,6 +21,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp 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.loading_note @Composable fun QuotedNote( @@ -105,7 +108,7 @@ fun QuotedNote( ) Spacer(Modifier.width(MaterialTheme.spacing.space100)) Text( - text = "Loading note...", + text = stringResource(Res.string.loading_note), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/UnsupportedKindBadge.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/UnsupportedKindBadge.kt index 870f9588..95410c3c 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/UnsupportedKindBadge.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/content/UnsupportedKindBadge.kt @@ -12,6 +12,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.TextStyle 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.unsupported_event_kind @Composable internal fun UnsupportedKindBadge(kind: Int?, style: TextStyle) { @@ -24,7 +27,7 @@ internal fun UnsupportedKindBadge(kind: Int?, style: TextStyle) { .padding(vertical = MaterialTheme.spacing.space75) ) { Text( - text = if (kind != null) "Unsupported event kind: $kind" else "Unsupported event kind", + text = if (kind != null) "Unsupported event kind: $kind" else stringResource(Res.string.unsupported_event_kind), style = style, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(MaterialTheme.spacing.space175) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/MetadataEventDetail.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/MetadataEventDetail.kt index a04d24cd..9952f93f 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/MetadataEventDetail.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/MetadataEventDetail.kt @@ -58,6 +58,15 @@ import press.mantra.compose.ui.view.state.MetadataEventDetailUIState import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.add_to_group +import mantra.composeapp.generated.resources.block_user +import mantra.composeapp.generated.resources.edit_profile +import mantra.composeapp.generated.resources.follow +import mantra.composeapp.generated.resources.follow_back +import mantra.composeapp.generated.resources.send_message +import mantra.composeapp.generated.resources.unfollow @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -181,7 +190,7 @@ fun MetadataEventDetail( onClick = onNavigateToEditProfile ) { Text( - "Edit profile" + stringResource(Res.string.edit_profile) ) } @@ -195,7 +204,7 @@ fun MetadataEventDetail( } ) { Text( - "Unfollow" + stringResource(Res.string.unfollow) ) } } else { @@ -207,9 +216,9 @@ fun MetadataEventDetail( ) { Text( text = if (metadataEventDetailUIState.followingActiveUserConnection != null) { - "follow back" + stringResource(Res.string.follow_back) } else { - "follow" + stringResource(Res.string.follow) } ) } @@ -252,7 +261,7 @@ fun MetadataEventDetail( modifier = Modifier.width(MaterialTheme.spacing.space125) ) - Text("Add to group") + Text(stringResource(Res.string.add_to_group)) } } @@ -273,7 +282,7 @@ fun MetadataEventDetail( modifier = Modifier.width(MaterialTheme.spacing.space125) ) - Text("Block user") + Text(stringResource(Res.string.block_user)) } } @@ -297,7 +306,7 @@ fun MetadataEventDetail( Spacer( modifier = Modifier.width(MaterialTheme.spacing.space125) ) - Text("Send message") + Text(stringResource(Res.string.send_message)) } } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt index 2a8aaac6..720ea55e 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt @@ -49,6 +49,11 @@ import press.mantra.compose.extensions.toFormattedTimeAndDateString import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +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 @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -87,7 +92,7 @@ fun TextNoteEventDetail( } }, title = { - Text("Post") + Text(stringResource(Res.string.post)) }, ) }, @@ -346,7 +351,7 @@ fun TextNoteEventDetail( if (inReplyToFeedListUIState.localNostrEvents.isEmpty()) { item { Text( - "Be the first to comment." + stringResource(Res.string.be_the_first_to_comment) ) } } else { @@ -363,7 +368,7 @@ fun TextNoteEventDetail( press.mantra.compose.ui.view.state.FeedListUIState.Error -> { item { Text( - text = "Something went wrong" + text = stringResource(Res.string.something_went_wrong) ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/dialogs/NewChatBottomSheetDialog.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/dialogs/NewChatBottomSheetDialog.kt index 924593f7..f2520e36 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/dialogs/NewChatBottomSheetDialog.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/dialogs/NewChatBottomSheetDialog.kt @@ -29,6 +29,12 @@ import press.mantra.compose.ui.composable.navigation.routes.Route import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.close +import mantra.composeapp.generated.resources.create_new_chat +import mantra.composeapp.generated.resources.direct_message_via_npub +import mantra.composeapp.generated.resources.start_a_group_chat @OptIn(ExperimentalMaterial3Api::class) @@ -55,7 +61,7 @@ fun NewChatBottomSheetDialog( horizontalAlignment = Alignment.CenterHorizontally ) { Text( - text = "Create new chat", + text = stringResource(Res.string.create_new_chat), textAlign = TextAlign.Center, style = MaterialTheme.typography.bodyLarge ) @@ -74,7 +80,7 @@ fun NewChatBottomSheetDialog( ) }, headlineContent = { - Text("Direct message via npub") + Text(stringResource(Res.string.direct_message_via_npub)) }, trailingContent = { Icon( @@ -101,7 +107,7 @@ fun NewChatBottomSheetDialog( }, headlineContent = { Text( - text = "Start a group chat" + text = stringResource(Res.string.start_a_group_chat) ) }, trailingContent = { @@ -129,7 +135,7 @@ fun NewChatBottomSheetDialog( Spacer( modifier = Modifier.width(MaterialTheme.spacing.space125) ) - Text("Close") + Text(stringResource(Res.string.close)) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/dialogs/StartDirectMessageToNpubOrNip05Dialog.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/dialogs/StartDirectMessageToNpubOrNip05Dialog.kt index a3cdc745..17fb12c1 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/dialogs/StartDirectMessageToNpubOrNip05Dialog.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/dialogs/StartDirectMessageToNpubOrNip05Dialog.kt @@ -46,6 +46,11 @@ import press.mantra.compose.ui.theme.spacing import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.input_npub_or_nip05 +import mantra.composeapp.generated.resources.start_chat +import mantra.composeapp.generated.resources.start_chat_via_npub_or_nip05 @Composable fun StartDirectMessageToNpubOrNip05Dialog( @@ -91,7 +96,7 @@ fun StartDirectMessageToNpubOrNip05Dialog( modifier = Modifier.fillMaxWidth().padding( horizontal = MaterialTheme.spacing.space250, ), - text = "Start chat via npub or nip05", + text = stringResource(Res.string.start_chat_via_npub_or_nip05), style = MaterialTheme.typography.labelMedium, textAlign = TextAlign.Center ) @@ -99,7 +104,7 @@ fun StartDirectMessageToNpubOrNip05Dialog( modifier = Modifier.focusRequester(npubFieldFocus), state = textFieldState, placeholder = { - Text("Input npub... or nip05") + Text(stringResource(Res.string.input_npub_or_nip05)) }, trailingIcon = { IconButton( @@ -166,7 +171,7 @@ fun StartDirectMessageToNpubOrNip05Dialog( } } ) { - Text("Start chat") + Text(stringResource(Res.string.start_chat)) Spacer( modifier = Modifier.width(MaterialTheme.spacing.space125) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/feed/EventListView.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/feed/EventListView.kt index ea28444a..d72ea62b 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/feed/EventListView.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/feed/EventListView.kt @@ -25,6 +25,11 @@ import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent import kotlin.time.Instant import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.loading_author_information_2 +import mantra.composeapp.generated.resources.malformed_note +import mantra.composeapp.generated.resources.reposted @Composable fun press.mantra.compose.database.model.intermdiate.LocalNostrEvent.ListView( @@ -66,7 +71,7 @@ fun press.mantra.compose.database.model.intermdiate.LocalNostrEvent.ListView( color = press.mantra.compose.ui.composable.widgets.profile.ProfileColor.fromPublicKey(profile?.publicKey ?: nostrEvent.pubKey) ) Text( - text = "reposted", + text = stringResource(Res.string.reposted), style = MaterialTheme.typography.labelSmall ) } @@ -92,7 +97,7 @@ fun press.mantra.compose.database.model.intermdiate.LocalNostrEvent.ListView( ).containedPost() if (containedPost != null) { - Text("Loading author information...") + Text(stringResource(Res.string.loading_author_information_2)) Text( containedPost.content ) @@ -100,7 +105,7 @@ fun press.mantra.compose.database.model.intermdiate.LocalNostrEvent.ListView( text = Instant.fromEpochSeconds(containedPost.createdAt).toFormattedTimeAndDateString() ) } else { - Text("Malformed note") + Text(stringResource(Res.string.malformed_note)) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/feed/TextNoteFeedItem.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/feed/TextNoteFeedItem.kt index 822e5d4b..03113927 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/feed/TextNoteFeedItem.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/feed/TextNoteFeedItem.kt @@ -26,6 +26,10 @@ import androidx.compose.ui.unit.dp import press.mantra.compose.extensions.toFormattedTimeAndDateString 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.loading_author_information +import mantra.composeapp.generated.resources.loading_information @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -63,7 +67,7 @@ internal fun TextNoteFeedItem( ), style = MaterialTheme.typography.labelLargeEmphasized, color = press.mantra.compose.ui.composable.widgets.profile.ProfileColor.fromPublicKey(localInReplyToNostrEvent.nostrEvent.pubKey), - text = localInReplyToNostrEvent.profile?.humanReadableNameOrPubkey() ?: "loading information..." + text = localInReplyToNostrEvent.profile?.humanReadableNameOrPubkey() ?: stringResource(Res.string.loading_information) ) } } @@ -116,7 +120,7 @@ internal fun TextNoteFeedItem( } } else { Text( - text = "Loading author information" + text = stringResource(Res.string.loading_author_information) ) } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/ChatMessageListViewModel.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/ChatMessageListViewModel.kt index e8a53e83..231908ea 100755 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/ChatMessageListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/ChatMessageListViewModel.kt @@ -94,6 +94,16 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import press.mantra.compose.ui.theme.spacing import press.mantra.compose.ui.composable.widgets.Decorative +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.currently_no_messages_have_been_shared +import mantra.composeapp.generated.resources.loading +import mantra.composeapp.generated.resources.no_chat_message_relays_were_found_for_this +import mantra.composeapp.generated.resources.private_to_you +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 class ChatMessageListViewModel( initialChatMessageListUIState: ChatMessageListUIState, @@ -399,7 +409,7 @@ class ChatMessageListViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "Something went wrong", + text = stringResource(Res.string.something_went_wrong), ) } } @@ -420,7 +430,7 @@ class ChatMessageListViewModel( ) Text( modifier = Modifier.padding(MaterialTheme.spacing.space250), - text = "Currently no messages have been shared.\nBreak the ice.", + text = stringResource(Res.string.currently_no_messages_have_been_shared), textAlign = TextAlign.Center ) @@ -489,7 +499,7 @@ class ChatMessageListViewModel( modifier = Modifier.weight(1f) ) { Text( - text = "No chat message relays were found for this user.", + text = stringResource(Res.string.no_chat_message_relays_were_found_for_this), textAlign = TextAlign.Center, style = MaterialTheme.typography.labelSmall ) @@ -660,7 +670,7 @@ class ChatMessageListViewModel( text = if (localChatMessage.chatMessage.isUserMessage) { "Private to ${nameFor(localChatMessage.chatMessage.directMessageRecipientPublicKey)}" } else { - "Private to you" + stringResource(Res.string.private_to_you) }, color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.labelSmall @@ -750,7 +760,7 @@ class ChatMessageListViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "Loading", + text = stringResource(Res.string.loading), textAlign = TextAlign.Center ) Spacer( @@ -875,7 +885,7 @@ private fun ProposalsAwaitingYouNotice( ) { Text( text = if (single != null) { - "Waiting for your signature" + stringResource(Res.string.waiting_for_your_signature) } else { "${proposals.size} proposals are waiting for your signature" }, @@ -896,7 +906,7 @@ private fun ProposalsAwaitingYouNotice( // thing, so a member reading down the room is not asked twice in two // vocabularies. Text( - text = "Review", + text = stringResource(Res.string.review), style = MaterialTheme.typography.labelLarge ) @@ -1062,7 +1072,7 @@ private fun RitualNotice( ) { append( if (chatMessage.isUserMessage) { - "You" + stringResource(Res.string.you) } else { localChatMessage.profile?.humanReadableNameOrPubkey() ?: chatMessage.senderPublicKey.shortened() @@ -1087,7 +1097,7 @@ private fun RitualNotice( if (isRequest) { Text( - text = "Review", + text = stringResource(Res.string.review), style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.primary ) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/ChatRoomListViewModel.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/ChatRoomListViewModel.kt index 61e99403..a407647b 100755 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/ChatRoomListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/ChatRoomListViewModel.kt @@ -41,6 +41,11 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.launch import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.loading +import mantra.composeapp.generated.resources.no_messages_go_to_a_profile_and_send_them_a +import mantra.composeapp.generated.resources.something_went_wrong class ChatRoomListViewModel( initialChatRoomListUIState: ChatRoomListUIState, @@ -154,7 +159,7 @@ class ChatRoomListViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "Something went wrong", + text = stringResource(Res.string.something_went_wrong), ) } } @@ -167,7 +172,7 @@ class ChatRoomListViewModel( if (chatRoomListUIState.chatRoomList.isEmpty()) { Text( - text = "No messages. Go to a profile and send them a message." + text = stringResource(Res.string.no_messages_go_to_a_profile_and_send_them_a) ) } else { LazyColumn( @@ -236,7 +241,7 @@ class ChatRoomListViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "Loading", + text = stringResource(Res.string.loading), textAlign = TextAlign.Center ) Spacer( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/FeedListViewModel.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/FeedListViewModel.kt index acbc0558..beb7a10b 100755 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/FeedListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/FeedListViewModel.kt @@ -35,6 +35,11 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.loading +import mantra.composeapp.generated.resources.no_events_were_found +import mantra.composeapp.generated.resources.something_went_wrong class FeedListViewModel( initialFeedListUIState: FeedListUIState, @@ -121,7 +126,7 @@ class FeedListViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "Something went wrong", + text = stringResource(Res.string.something_went_wrong), ) } } @@ -135,7 +140,7 @@ class FeedListViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "No events were found", + text = stringResource(Res.string.no_events_were_found), ) } } else { @@ -172,7 +177,7 @@ class FeedListViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "Loading", + text = stringResource(Res.string.loading), textAlign = TextAlign.Center ) Spacer( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/FollowersListViewModel.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/FollowersListViewModel.kt index a4e4c6c6..04e246dd 100755 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/FollowersListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/FollowersListViewModel.kt @@ -31,6 +31,11 @@ import kotlinx.coroutines.IO import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.loading +import mantra.composeapp.generated.resources.no_events_were_found +import mantra.composeapp.generated.resources.something_went_wrong class FollowersListViewModel( initialFollowingListUIState: press.mantra.compose.ui.view.state.FollowersListUIState, @@ -115,7 +120,7 @@ class FollowersListViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "Something went wrong", + text = stringResource(Res.string.something_went_wrong), ) } } @@ -129,7 +134,7 @@ class FollowersListViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "No events were found", + text = stringResource(Res.string.no_events_were_found), ) } } else { @@ -160,7 +165,7 @@ class FollowersListViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "Loading", + text = stringResource(Res.string.loading), textAlign = TextAlign.Center ) Spacer( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/FollowingListViewModel.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/FollowingListViewModel.kt index 7c62724b..345ecdef 100755 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/FollowingListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/FollowingListViewModel.kt @@ -31,6 +31,11 @@ import kotlinx.coroutines.IO import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.loading +import mantra.composeapp.generated.resources.no_events_were_found +import mantra.composeapp.generated.resources.something_went_wrong class FollowingListViewModel( initialFollowingListUIState: press.mantra.compose.ui.view.state.FollowingListUIState, @@ -114,7 +119,7 @@ class FollowingListViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "Something went wrong", + text = stringResource(Res.string.something_went_wrong), ) } } @@ -128,7 +133,7 @@ class FollowingListViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "No events were found", + text = stringResource(Res.string.no_events_were_found), ) } } else { @@ -159,7 +164,7 @@ class FollowingListViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "Loading", + text = stringResource(Res.string.loading), textAlign = TextAlign.Center ) Spacer( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/InReplyToViewModel.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/InReplyToViewModel.kt index e12bd9f8..a6223751 100755 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/InReplyToViewModel.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/InReplyToViewModel.kt @@ -36,6 +36,11 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import kotlin.time.Instant import press.mantra.compose.ui.theme.spacing +import mantra.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.loading +import mantra.composeapp.generated.resources.no_events_were_found +import mantra.composeapp.generated.resources.something_went_wrong class InReplyToViewModel( val nostrEvent: press.mantra.compose.database.model.NostrEvent, @@ -125,7 +130,7 @@ class InReplyToViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "Something went wrong", + text = stringResource(Res.string.something_went_wrong), ) } } @@ -139,7 +144,7 @@ class InReplyToViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "No events were found", + text = stringResource(Res.string.no_events_were_found), ) } } else { @@ -176,7 +181,7 @@ class InReplyToViewModel( modifier = Modifier.height(MaterialTheme.spacing.space600) ) Text( - text = "Loading", + text = stringResource(Res.string.loading), textAlign = TextAlign.Center ) Spacer( diff --git a/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/StringCatalogueJvmTest.kt b/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/StringCatalogueJvmTest.kt new file mode 100644 index 00000000..3cfd25e0 --- /dev/null +++ b/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/StringCatalogueJvmTest.kt @@ -0,0 +1,48 @@ +package press.mantra.compose.ui + +import kotlinx.coroutines.runBlocking +import mantra.composeapp.generated.resources.Res +import mantra.composeapp.generated.resources.currently_no_messages_have_been_shared +import mantra.composeapp.generated.resources.don_t_sign +import mantra.composeapp.generated.resources.something_went_wrong +import org.jetbrains.compose.resources.getString +import kotlin.test.Test +import kotlin.test.assertEquals + +/** + * The string catalogue reads back what the source used to say. + * + * Moving 315 literals out of composables and into `strings.xml` is a rename that a + * compiler cannot check: every call site still compiles if a resource holds the wrong + * text, and the mistake surfaces as wrong words on a screen nobody has opened. + * + * The escapes are the part worth a test rather than an assumption. A string going into an + * XML resource needs `'` escaped as `\'` and `%` doubled, and the generated `.cvr` stores + * the escaped form verbatim -- base64 of `We couldn\'t find...`. Whether the backslash is + * still there when `stringResource` hands the text to a `Text` is a property of the + * resources library, not of the extraction, and the failure mode is an app that literally + * displays `Don\'t sign`. + * + * `getString` is the non-composable reader for the same resources, so this can assert it + * without a composition. + */ +class StringCatalogueJvmTest { + + @Test + fun `an apostrophe survives the round trip through the catalogue`() = runBlocking { + assertEquals("Don't sign", getString(Res.string.don_t_sign)) + } + + @Test + fun `an escaped newline comes back as a newline`() = runBlocking { + assertEquals( + "Currently no messages have been shared.\nBreak the ice.", + getString(Res.string.currently_no_messages_have_been_shared), + ) + } + + @Test + fun `a plain string comes back unchanged`() = runBlocking { + assertEquals("Something went wrong", getString(Res.string.something_went_wrong)) + } +} diff --git a/docs/scripts/m3-extract-strings.py b/docs/scripts/m3-extract-strings.py new file mode 100755 index 00000000..5a399375 --- /dev/null +++ b/docs/scripts/m3-extract-strings.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 +"""Move literal UI strings out of composables and into the resource catalogue. + +Handles the simple case only: a `text = "..."` or `Text("...")` whose literal contains no +Kotlin interpolation. Interpolated strings need format placeholders and an argument order +decided per site, which is not something to do blind. + +Resource names are derived from the string's content, snake_cased and truncated at a word +boundary. That is the conventional shape for an automated extraction and it has a known +cost: rewording the copy makes the name a little stale. The alternative -- a name derived +from the string's *purpose* -- needs somebody to read all 285 call sites, and a wrong +purpose in a name is worse than a slightly dated one. + +Usage: + m3-extract-strings.py dry run, prints the plan + m3-extract-strings.py --apply +""" +import os, re, sys, io, collections + +UI = 'composeApp/src/commonMain/kotlin/press/mantra/compose/ui' +STRINGS = 'composeApp/src/commonMain/composeResources/values/strings.xml' +DRY = '--apply' not in sys.argv + +# Literals inside a `Text(...)` call, with no `$` inside. +# +# The first version of this matched a bare `text = "…"` anywhere, which is wrong: `text` +# is an ordinary parameter name and the tree uses it on data classes too. It rewrote +# `NavigationUIState.Loading(text = "…")` -- not a composable -- and the compiler caught +# it with "@Composable invocations can only happen from the context of a @Composable +# function". So the call is brace-matched instead, and only literals genuinely inside a +# `Text(` are touched. +def text_call_spans(source): + """Character ranges of every `Text(` / `BasicText(` call, brace-matched. + + Anchoring to the call is what keeps `NavigationUIState.Loading(text = "…")` out of + the results. `text` is an ordinary parameter name and the tree uses it on data + classes too; an earlier version matched a bare `text = "…"` anywhere and rewrote a + non-composable, which the compiler caught with "@Composable invocations can only + happen from the context of a @Composable function". + """ + spans = [] + for m in re.finditer(r'\b(?:Basic)?Text\s*\(', source): + depth, i = 0, m.end() - 1 + while i < len(source): + if source[i] == '(': + depth += 1 + elif source[i] == ')': + depth -= 1 + if depth == 0: + spans.append((m.start(), i)) + break + i += 1 + return spans + + +def kotlin_strings(source): + """Every top-level Kotlin string literal, as (start, end, body, interpolated). + + A regex cannot do this. `"a ${if (n == 1) "chunk" else "chunks"} b"` contains two + inner literals that belong to the outer template, and matching quote pairs left to + right pulls them out as strings of their own -- which is how an earlier version of + this script decided "chunk" and "note" were UI strings worth translating. + + So the source is scanned: on an opening quote, walk forward tracking `${` depth and + recursing over nested literals, and stop at the closing quote that is at depth zero. + Raw strings and escapes are skipped rather than parsed, which is enough here. + """ + out, i, n = [], 0, len(source) + while i < n: + c = source[i] + if c == '"' and source[i:i + 3] == '\"\"\"': + end = source.find('\"\"\"', i + 3) + i = n if end == -1 else end + 3 + continue + if c != '"': + i += 1 + continue + j, depth, interpolated = i + 1, 0, False + while j < n: + ch = source[j] + if ch == '\\': + j += 2 + continue + if ch == '$' and j + 1 < n and (source[j + 1] == '{' or source[j + 1].isalpha()): + interpolated = True + if source[j + 1] == '{': + depth += 1 + j += 2 + continue + if depth > 0: + if ch == '{': + depth += 1 + elif ch == '}': + depth -= 1 + elif ch == '"': + # a literal inside the interpolation: skip it whole + k = j + 1 + while k < n and source[k] != '"': + k += 2 if source[k] == '\\' else 1 + j = k + j += 1 + continue + if ch == '"': + break + if ch == '\n': + break + j += 1 + if j < n and source[j] == '"': + out.append((i, j + 1, source[i + 1:j], interpolated)) + i = j + 1 + else: + i += 1 + return out + + +def literals_in_text_calls(source): + """(start, end, value) for each plain, whole literal inside a Text( call.""" + spans = text_call_spans(source) + found = [] + for start, end, value, interpolated in kotlin_strings(source): + if interpolated or not value.strip(): + continue + if not any(a <= start <= b for a, b in spans): + continue + line_start = source.rfind('\n', 0, start) + 1 + line = source[line_start:source.find('\n', start)] + if line.lstrip().startswith(('//', '*', '/*')): + continue + # A fragment of a concatenation. `"a " + x + " b"` is one sentence in three + # pieces, and " b" is not a translatable unit -- word order differs between + # languages, so a translator handed " b" has nothing to work with. These go with + # the interpolated strings, which need format placeholders and a human. + before = source[:start].rstrip() + after = source[end:].lstrip() + if before.endswith('+') or after.startswith('+'): + continue + if value != value.strip() or not any(c.isalpha() for c in value): + continue + if before.endswith('append('): + continue + # The same fragment problem one level out. A pluralisation reads + # + # (if (n == 2) "event" else "events") + + # + # so neither literal is adjacent to the `+` -- the parenthesis is. Testing the + # line instead catches those four sites while leaving a genuine either/or alone: + # + # text = if (session == null) "Start key ceremony" else "Try again" + # + # has no `+` on its line and both branches are whole strings. + stripped = line.strip() + if stripped.endswith('+') or stripped.startswith('+'): + continue + found.append((start, end, value)) + return found + + +def resource_name(text, taken): + slug = re.sub(r'[^a-z0-9]+', '_', text.lower()).strip('_') + words = slug.split('_') + name, out = '', [] + for w in words: + if len(name) + len(w) + 1 > 44: + break + out.append(w) + name = '_'.join(out) + name = name or 'string' + if name[0].isdigit(): + name = 's_' + name + base, i = name, 2 + while name in taken: + name = f'{base}_{i}' + i += 1 + return name + + +def xml_escape(text): + """XML entities only. + + Compose Resources is not aapt, and the difference is a shipped bug rather than a + detail. An earlier version of this escaped apostrophes as `\\'` and doubled `%`, which + is what android's resource compiler requires. Compose Resources does neither: + `getString` returned `Don\\'t sign`, backslash included, and + StringCatalogueJvmTest caught it. + + It *does* process `\\n`, which the same test asserts -- so escape handling here is + partial rather than absent, and worth checking per escape rather than assuming a + family. + + `%` is left alone. Nothing in this catalogue contains one, and if something does + later it only matters for an entry passed through `getString(resource, args)`. + """ + return text.replace('&', '&').replace('<', '<').replace('>', '>') + + +def main(): + occurrences = collections.defaultdict(list) + for root, _, files in os.walk(UI): + for f in sorted(files): + if not f.endswith('.kt'): + continue + path = os.path.join(root, f) + src = open(path, encoding='utf-8').read() + for start, _end, value in literals_in_text_calls(src): + occurrences[value].append((path, src[:start].count('\n') + 1)) + + taken, names = set(), {} + for text in sorted(occurrences): + names[text] = resource_name(text, taken) + taken.add(names[text]) + + total = sum(len(v) for v in occurrences.values()) + print(f'{len(occurrences)} distinct strings, {total} occurrences') + if DRY: + for text in sorted(occurrences)[:10]: + print(f' {names[text]:46s} {text[:60]!r}') + print(' ...') + return 0 + + # Rewrite the call sites. + rewritten = 0 + for root, _, files in os.walk(UI): + for f in sorted(files): + if not f.endswith('.kt'): + continue + path = os.path.join(root, f) + src = io.open(path, encoding='utf-8').read() + found = literals_in_text_calls(src) + changed = len(found) + # Right to left, so earlier offsets stay valid. + for start, end, value in reversed(found): + src = src[:start] + f'stringResource(Res.string.{names[value]})' + src[end:] + if changed: + for imp in ('import mantra.composeapp.generated.resources.Res', + 'import org.jetbrains.compose.resources.stringResource'): + if imp not in src: + lines = src.split('\n') + idx = max(i for i, l in enumerate(lines) if l.startswith('import ')) + lines.insert(idx + 1, imp) + src = '\n'.join(lines) + # the generated accessors are one per string, under the same package + need = sorted({names[t] for t in occurrences + if any(p == path for p, _ in occurrences[t])}) + lines = src.split('\n') + idx = max(i for i, l in enumerate(lines) if l.startswith('import ')) + for nm in reversed(need): + imp = f'import mantra.composeapp.generated.resources.{nm}' + if imp not in src: + lines.insert(idx + 1, imp) + src = '\n'.join(lines) + io.open(path, 'w', encoding='utf-8').write(src) + rewritten += changed + print(f'{rewritten} call sites rewritten') + + # Write the catalogue. + existing = io.open(STRINGS, encoding='utf-8').read() + entries = '\n'.join( + f' {xml_escape(t)}' + for t in sorted(occurrences, key=lambda x: names[x])) + io.open(STRINGS, 'w', encoding='utf-8').write( + existing.replace('', entries + '\n')) + print(f'{len(occurrences)} entries written to {STRINGS}') + return 0 + + +if __name__ == '__main__': + sys.exit(main())