Files
mantra-kmp/composeApp/src/commonMain/composeResources/values/strings.xml

317 lines
24 KiB
XML
Raw Normal View History

2026-06-15 14:39:46 +02:00
<resources>
fix: sentence-case every UI string, settle the product name, and empty the dead catalogue Phase 4, first step, of docs/material-design-conformance.md. M3's style guide is unambiguous: "All text, including titles, headings, labels, menu items, navigation components, app bars, and buttons should use sentence-style capitalization. ... Don't use title case capitalization." The tree was title case throughout. **100 occurrences across 60 distinct strings**, in two passes, and the second pass is the interesting one. The first pass matched `[A-Z][a-z]+( [A-Z][a-z]+)+` in a `text =`, `Text(` or `contentDescription =` position and found 41 strings, 73 occurrences: "Add Chapter", "Sign In", "Key Package Management", "Publish New Key Package". Then the audit reported zero and the app still had "Invite a Friend" on its first screen. Two holes. The pattern required every word after the first to be capitalised, so anything with an article in it survived -- "Invite a Friend", "Add to Group", "Name of Artifact", "Sign in to Npub". And it read one line at a time, so a `Text(` whose literal sat on the next line was invisible. A whole-file scan allowing lowercase articles found 19 more strings, 27 occurrences. **Sample data is deliberately left in title case.** "Steve Biko", "John Doe", "Frank Talk", "To Kill a Mockingbird", "Man With A Plan", "Woman Of Few Words" are people and titles of works, and title case is how those are written. The first audit swept them up and reported 67 offenders where the real number was 41, which is the kind of number that teaches a reader to ignore the tool. Also untouched: the KDoc reference to iOS's own "Increase Contrast" setting, which is Apple's capitalisation of Apple's setting, and `logger.d("Queried Sync")`, which is written for whoever is reading logcat. **Two strings changed meaning rather than just case.** "Sign in to Npub" became "Sign in with an npub" -- npub is a protocol term, lowercase everywhere else in this app, and you sign in *with* one rather than *to* it. "Lightning Bolt", a content description, became "Lightning payment": M3's rule for a description is to name the purpose rather than the picture, and "bolt" is the picture. **The product has one name now, and it is Mantra.** The launcher label, the desktop window title, the landing screen and the package all said Mantra; the home screen's app bar said "Torch" and `composeResources`' `app_name` said "Machankura". The app bar is fixed. `UserAgent.APP_NAME` still says "Torch" and is left alone on purpose -- it goes on the wire to relay operators, so it is a network identity question rather than a content one, and a comment at the call site says so. **The two destructive actions now say what they do.** "Leave group" and "Delete group" are `TextButton`s that fire immediately, with no confirmation step and nothing stating the consequence. M3: "Tell users what will happen if they take an action and how they can undo it." Read out of the repository rather than guessed, because saying the wrong thing about a destructive action is worse than saying nothing. `leaveChatRoom` sets `leftGroupAt` and posts a line to the room; `softDeleteChatRoom` sets `deletedAt` on the local row and nothing else. So: "Posts a line to the room saying you left, and lets you delete it from this device afterwards", and "Removes the room from this device. The messages stay on the relays and with the other members." The second matters most -- a button labelled "Delete group" with no qualifier invites the belief that the messages are gone, which is the opposite of true. **1101 dead strings deleted.** `composeResources/values/strings.xml` held the phoenix wallet fork's whole catalogue -- notification channels, electrum settings, swap timeouts -- and **nothing referenced any of it**. The tree's only two `stringResource` calls are both commented out, and one of them names an `R.string`, which does not exist in a Compose Multiplatform resource set at all. Keeping them made the file look like the app's catalogue while the app's actual 332 strings sat in composables. It now holds `app_name` and a note about what happens next. A trap for the next person, recorded in the file: the compose resources plugin reports an XML comment containing a double hyphen only as "XML file ... is not valid. Check the file content." XML forbids `--` inside comments, and this commit hit it while writing that note. **The audit's check is now a script, for the reason the second pass exists.** `docs/scripts/m3-title-case.py` scans whole files, allows articles, excludes sample data by name and skips logger calls. Budget ratcheted to 0. The grep it replaces was wrong in three ways and reported success anyway, which is worse than not checking. **Tests.** 944 pass, 595 jvm over 72 classes and 349 android over 44, unchanged. The debug apk installs and runs on emulator-5554. `m3-audit.sh --check` exits 0. The 332 literals themselves are the next commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:27:26 +02:00
<!--
The app's own string catalogue. Nearly empty, for now.
This file previously held 1101 strings inherited from the phoenix wallet fork
(notification channel titles, electrum server settings, swap timeouts), and
app_name was "Machankura". Nothing referenced any of them: the only two
stringResource calls in the tree were both commented out, and one of them named an
R.string, which does not exist in a Compose Multiplatform resource set at all.
Keeping 1101 dead strings made this look like the app's catalogue while the app's
actual 334 strings sat inside composables.
The launcher label is a separate resource, in androidMain/res/values/strings.xml,
which is what AndroidManifest.xml points at. It already said "Mantra".
Externalising the 334 is the rest of phase 4 in
docs/material-design-conformance.md. They land here.
Note for whoever edits this: XML forbids a double hyphen inside a comment, so use
an em dash or a comma. The compose resources plugin reports that only as
"XML file ... is not valid. Check the file content."
-->
<string name="app_name">Mantra</string>
refactor: move 315 UI strings into the resource catalogue, and prove the escapes survive 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 <noreply@anthropic.com>
2026-09-08 01:38:31 +02:00
<string name="add_a_dialect_the_group_can_translate_into">Add a dialect the group can translate into</string>
<string name="add_artifact_to_library">Add artifact to library</string>
<string name="add_artifact_to_the_group_library">Add artifact to the group library</string>
<string name="add_chapter">Add chapter</string>
<string name="add_dialect">Add dialect</string>
<string name="add_to_group">Add to group</string>
<string name="add_translation">Add translation</string>
<string name="after_this_there_is_no_turning_back">After this there is no turning back.</string>
<string name="all_broadcasts_are_queued_so_that_we_can">All broadcasts are queued so that we can manage data usage on metered connections.</string>
<string name="any_amount">Any amount</string>
<string name="article">ARTICLE</string>
<string name="artifact_detail">Artifact detail</string>
<string name="as_long_as_you_control_your_keys_there_can">As long as you control your keys there can be no dispute about who YOU actually is.</string>
<string name="back">Back</string>
<string name="be_sure_to_keep_this_nsec_safe">Be sure to keep this nsec safe.</string>
<string name="be_the_first_to_comment">Be the first to comment.</string>
<string name="bio">Bio</string>
<string name="block_user">Block user</string>
<string name="cancel">Cancel</string>
<string name="change_account">Change account</string>
<string name="chapter_detail">Chapter detail</string>
<string name="chapter_name">Chapter name</string>
<string name="chapter_translation">Chapter translation</string>
<string name="chapters">Chapters</string>
<string name="choose_who_to_chat_with">Choose who to chat with</string>
<string name="close">Close</string>
<string name="copy_url">Copy URL</string>
<string name="country">Country</string>
<string name="create_chat">Create chat</string>
<string name="create_new_chat">Create new chat</string>
<string name="create_profile">Create profile</string>
<string name="create_project">Create project</string>
<string name="create_the_admins_group">Create the #admins group</string>
<string name="creating_new_chat">Creating new chat.</string>
<string name="currently_no_contacts_please_search_and_chat">Currently no contacts. Please search and chat with a few people.</string>
<string name="currently_no_messages_have_been_shared">Currently no messages have been shared.\nBreak the ice.</string>
<string name="delete_group">Delete group</string>
<string name="details">Details</string>
<string name="dialect_name">Dialect name</string>
<string name="dialects">Dialects</string>
<string name="direct_message_detail">Direct message detail</string>
<string name="direct_message_functionality_will_be_here">Direct message functionality will be here.</string>
<string name="direct_message_via_npub">Direct message via npub</string>
<string name="don_t_sign">Don't sign</string>
<string name="download">Download</string>
<string name="edit_profile">Edit profile</string>
<string name="eg_chapter_1_the_beginning">eg. Chapter 1 — The Beginning</string>
<string name="eg_first_edition">eg. First Edition</string>
<string name="eg_https_harper_com_2_kill_bird">eg. https://harper.com/2-kill-Bird</string>
<string name="eg_lesotho">eg. Lesotho</string>
<string name="eg_sesotho">eg. Sesotho</string>
<string name="eg_st">eg. st</string>
<string name="eg_to_kill_a_mocking_bird">eg. To Kill a Mocking Bird</string>
<string name="end_this">end this</string>
<string name="ended">ENDED</string>
<string name="enter_the_name_you_want_to_use_for_your">Enter the name you want to use for your group</string>
<string name="enter_the_name_you_want_to_use_for_your_2">Enter the name you want to use for your profile</string>
<string name="enter_the_nsec_or_npub_read_only_that_you">Enter the nsec or npub (read only) that you want to sign in as</string>
<string name="enter_the_translation_for_this_chunk">Enter the translation for this chunk</string>
<string name="events_are_indexed_so_that_we_can_deliver_a">Events are indexed so that we can deliver a premium local first experience.</string>
<string name="everything_else">Everything else</string>
<string name="everything_is_cryptographical_sound_just">Everything is cryptographical sound. Just announcing your profile to the world.</string>
<string name="everything_is_cryptographical_sound_just_2">Everything is cryptographical sound. Just indexing your profile on the device.</string>
<string name="everything_is_cryptographical_sound_just_3">Everything is cryptographical sound. Just need to queue your profile and announce it to the world.</string>
<string name="expired">Expired</string>
<string name="failed">✗ Failed</string>
<string name="follow">follow</string>
<string name="follow_back">follow back</string>
<string name="functionality_coming_soon">functionality coming soon.</string>
<string name="has_not_taken_part_yet">Has not taken part yet</string>
<string name="how_many_admins_have_to_approve_a_change">How many admins have to approve a change?</string>
<string name="how_many_members_will_it_take_to_sign">How many members will it take to sign?</string>
<string name="if_you_are_new_to_torch_or_just_want_to">If you are new to Torch or just want to create a fresh profile</string>
<string name="initial_version_label">Initial version label</string>
<string name="input_npub_or_nip05">Input npub... or nip05</string>
<string name="introduce_yourself">Introduce yourself</string>
<string name="invite">Invite</string>
<string name="invite_a_friend">Invite a friend</string>
<string name="invite_new_member">Invite new member</string>
<string name="it_is_cryptographical_secure_and">It is cryptographical secure, and decentralized, putting you in total control of your digital profile</string>
<string name="just_you_for_now">Just you for now</string>
<string name="keep_the_feed_alive">Keep the feed alive.</string>
<string name="key">Key</string>
<string name="key_package_management">Key package management</string>
<string name="language">Language</string>
<string name="learn_more">Learn more</string>
<string name="leave_group">Leave group</string>
<string name="library">Library</string>
<string name="lightning_invoice">Lightning invoice</string>
<string name="live">LIVE</string>
<string name="live_stream">Live stream</string>
<string name="loading">Loading</string>
<string name="loading_article">Loading article...</string>
<string name="loading_author_information">Loading author information</string>
<string name="loading_author_information_2">Loading author information...</string>
<string name="loading_information">loading information...</string>
<string name="loading_note">Loading note...</string>
<string name="loading_stream">Loading stream...</string>
<string name="lock_prompt_coming_soon">Lock prompt coming soon.</string>
<string name="malformed_note">Malformed note</string>
<string name="mantra">Mantra</string>
<string name="members">Members</string>
<string name="members_2">members</string>
<string name="name">Name</string>
<string name="name_eg_alan_turing">Name (eg. Alan Turing)</string>
<string name="name_eg_group_discussions">Name (eg. Group Discussions)</string>
<string name="name_of_artifact">Name of artifact</string>
<string name="network_relays">Network relays</string>
<string name="new_chat">New chat</string>
<string name="next">Next</string>
<string name="no_artifacts_exists_in_this_groups_library">No artifacts exists in this groups library.</string>
<string name="no_chapters">No chapters.</string>
<string name="no_chapters_yet">No chapters yet.</string>
<string name="no_chat_message_relays_were_found_for_this">No chat message relays were found for this user.</string>
<string name="no_chunks">No chunks.</string>
<string name="no_dialects_have_been_defined_in_this_group">No dialects have been defined in this group yet. Add one from the group's detail screen first.</string>
<string name="no_dialects_have_been_defined_in_this_group_2">No dialects have been defined in this group.</string>
<string name="no_events_were_found">No events were found</string>
<string name="no_messages_go_to_a_profile_and_send_them_a">No messages. Go to a profile and send them a message.</string>
<string name="no_one_selected_yet">No one selected yet</string>
<string name="no_one_to_add_yet">No one to add yet.</string>
<string name="no_projects_exists_in_this_group">No projects exists in this group.</string>
<string name="no_translations_yet">No translations yet.</string>
<string name="no_versions">No versions.</string>
<string name="not_now">Not now</string>
<string name="nothing_was_created_and_no_key_exists_it_is">Nothing was created and no key exists. It is safe to run it again.</string>
<string name="nsec_npub_nip_05_static_address">nsec, npub, nip-05 (static address)</string>
<string name="open_chat">Open chat</string>
<string name="original">Original</string>
<string name="original_text">Original text</string>
<string name="original_text_markdown">Original text (markdown)</string>
<string name="paid">✓ Paid</string>
<string name="paste_the_chapter_s_markdown_blank_lines">Paste the chapter's markdown. Blank lines separate paragraphs into chunks.</string>
<string name="pay">Pay</string>
<string name="pay_now">Pay now</string>
<string name="post">Post</string>
<string name="post_functionality_coming_soon">post functionality coming soon.</string>
<string name="private_to_you">Private to you</string>
<string name="profile">Profile</string>
<string name="profile_is_ready">Profile is ready</string>
<string name="profile_keys">Profile keys</string>
<string name="profiles">Profiles</string>
<string name="projects">Projects</string>
<string name="proposals">Proposals</string>
<string name="propose">Propose</string>
<string name="propose_artifact">Propose artifact</string>
<string name="propose_chapter">Propose chapter</string>
<string name="propose_dialect">Propose dialect</string>
<string name="propose_translation">Propose translation</string>
<string name="publish_new_key_package">Publish new key package</string>
<string name="re_broadcast">Re-broadcast</string>
<string name="read_to_the_end_of_the_list_to_sign">Read to the end of the list to sign.</string>
<string name="ready_to_sign">Ready to sign</string>
<string name="recents">Recents</string>
<string name="reindex_events">Reindex events</string>
<string name="reposted">reposted</string>
<string name="review">Review</string>
<string name="review_and_confirm">Review and confirm</string>
<string name="review_and_contribute">Review and contribute</string>
<string name="review_and_join">Review and join</string>
<string name="say_what_now">Say what now?</string>
<string name="search">Search</string>
<string name="search_for_people_and_chat_with_them_first">Search for people and chat with them first — everyone you know locally shows up here.</string>
<string name="search_hashtags">Search hashtags</string>
<string name="search_member_functionality">Search member functionality</string>
<string name="search_message_functionality_will_be_here">Search message functionality will be here.</string>
<string name="see_how_deep_the_rabbit_hole_goes">See how deep the rabbit hole goes</string>
<string name="select_a_wallet">Select a wallet</string>
<string name="send_message">Send message</string>
<string name="share_profile">Share profile</string>
<string name="shared_key">Shared key</string>
<string name="sign">Sign</string>
<string name="sign_in">Sign in</string>
<string name="sign_in_to_nsec">Sign in to nsec</string>
<string name="sign_in_to_torch_via_nsec_or_remote_signer">Sign in to Torch via nsec, or remote signer</string>
<string name="sign_in_with_an_npub">Sign in with an npub</string>
<string name="sign_out">Sign out</string>
<string name="sign_with_the_group_s_key">Sign with the group's key</string>
<string name="signed_their_part">Signed their part</string>
<string name="skip_for_now">Skip for now</string>
<string name="something_went_wrong">Something went wrong</string>
<string name="something_went_wrong_and_we_were_unable_to">Something went wrong and we were unable to sign in to your provided profile. Please try again later.</string>
<string name="something_went_wrong_and_we_were_unable_to_2">Something went wrong and we were unable to sign you up. Please try again later.</string>
<string name="something_went_wrong_and_we_were_unable_to_3">Something went wrong and we were unable to write a new note. Please try again later.</string>
<string name="source_dialect">Source dialect</string>
<string name="start">Start</string>
<string name="start_a_group_chat">Start a group chat</string>
<string name="start_chat">Start chat</string>
<string name="start_chat_via_npub_or_nip05">Start chat via npub or nip05</string>
<string name="start_key_ceremony">Start key ceremony</string>
<string name="startup_error">Startup error</string>
<string name="taking_part_with_these_members">Taking part with these members</string>
<string name="tap_to_load">Tap to load</string>
<string name="tell_friends_to_join_you_so_your_feed_stays">Tell friends to join you so your feed stays lively and fresh.</string>
<string name="the_above_will_be_your_new_note">The above will be your new note.</string>
<string name="the_above_will_be_your_profile">The above will be your profile.</string>
<string name="the_ceremony_was_abandoned">The ceremony was abandoned.</string>
<string name="the_group_can_hold_one_key_together_split_so">The group can hold one key together, split so that no single member holds it. Signing with it takes a quorum.</string>
<string name="the_group_has_a_shared_key">The group has a shared key.</string>
<string name="this_artifact_has_no_version_for_a">This artifact has no version for a translation to be of.</string>
<string name="this_artifact_has_no_version_for_a_chapter">This artifact has no version for a chapter to attach to.</string>
<string name="this_chapter_has_no_chunks">This chapter has no chunks.</string>
<string name="this_decides_who_can_change_the_group_later">This decides who can change the group later. You can't switch afterwards.</string>
<string name="this_group_has_no_shared_key_so_it_cannot">This group has no shared key, so it cannot sign them in.</string>
<string name="this_group_has_not_been_asked_to_sign">This group has not been asked to sign anything yet.</string>
<string name="this_is_fixed_once_the_ceremony_runs">This is fixed once the ceremony runs. Changing it later means generating a new key.</string>
<string name="this_is_the_last_thing_the_ceremony_needs">This is the last thing the ceremony needs from you.</string>
<string name="this_will_be_shown_when_people_open_the_chat">This will be shown when people open the chat for more details.</string>
<string name="this_will_be_shown_when_people_open_your">This will be shown when people open your profile.</string>
<string name="this_will_be_the_display_name_for_this_chat">This will be the display name for this chat room.</string>
<string name="this_will_be_the_display_name_for_your">This will be the display name for your profile and also important for search.</string>
<string name="this_will_give_you_read_only_access_to_the">This will give you read only access to the profile.</string>
<string name="this_will_give_you_write_access_to_the">This will give you write access to the profile.</string>
<string name="torch_will_be_broadcast_what_you_publish_to">Torch will be broadcast what you publish to a distributed set of relays so that it's decentralized.</string>
<string name="translate_chunk">Translate chunk</string>
<string name="translate_into_which_dialect">Translate into which dialect?</string>
<string name="translated_text">Translated text</string>
<string name="translation">Translation</string>
<string name="translation_detail">Translation detail</string>
<string name="translations">Translations</string>
<string name="transmit_note">Transmit note</string>
<string name="trending_notes_functionality_coming_soon_in">Trending notes functionality coming soon. In the meantime search for what you are looking for.</string>
<string name="try_again">Try again</string>
<string name="type_out_what_you_would_like_to_publish">Type out what you would like to publish</string>
<string name="unfollow">Unfollow</string>
<string name="unsupported_event_kind">Unsupported event kind</string>
<string name="untitled_article">Untitled article</string>
<string name="url">Url</string>
<string name="version">Version</string>
<string name="versions">Versions</string>
<string name="view_and_accept_invites_you_may_have">View and accept invites you may have received to stay connected with others.</string>
<string name="view_invites">View invites</string>
<string name="waiting_for_you">Waiting for you</string>
<string name="waiting_for_your_signature">Waiting for your signature</string>
<string name="we_are_looking_for_your_profile_on_as_many">We are looking for your profile on as many relays as possible. Nostr aims to be decentralized by distributing data to multiple nodse.</string>
<string name="we_are_searching_the_internet_to_find_your">We are searching the internet to find your profile and complete sign in.</string>
<string name="we_couldn_t_find_the_local_profile_please">We couldn't find the local profile. Please try again later.</string>
<string name="we_couldn_t_find_your_nostr_event_please_try">We couldn't find your nostr event. Please try again later.</string>
<string name="what_s_your_comment_on_the_below">What's your comment on the below</string>
<string name="what_s_your_reply_to_the_above">What's your reply to the above</string>
<string name="what_should_people_know_about_you">What should people know about you?</string>
<string name="what_vibrations_do_you_want_to_send_out">What vibrations do you want to send out?</string>
<string name="what_will_be_discussed_in_this_chat_room">What will be discussed in this chat room.</string>
<string name="who_will_you_be_passing_the_aux_to">Who will you be passing the aux to?</string>
<string name="you">You</string>
<string name="you_and_1_other">You and 1 other</string>
<string name="you_are_about_to_create_a_nostr_profile">You are about to create a NOSTR profile.</string>
<string name="you_can_still_carry_on_and_invite_people">You can still carry on and invite people later.</string>
<string name="you_will_be_in_full_control_of_this_profile">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.</string>
<string name="your_profile_is_almost_ready_just_getting_it">Your profile is almost ready... just getting it's first cryptographic signature together.</string>
<string name="your_share_of_it_is_on_this_device_only_your">Your share of it is on this device only. Your wallet backup restores it — nobody else's share can.</string>
refactor: make the 40 interpolated UI strings format strings, and assert the argument order Phase 4, third step, of docs/material-design-conformance.md. `Text("Add chapter to ${uiState.artifact.name}")` becomes a resource holding `Add chapter to %1$s` and a call passing the expression. 49 call sites. Literals in composables go 76 -> 39; `stringResource` goes 374 -> 424. **A silent bug in the previous commit's extractor, found by this one.** Imports were tested with `statement in source`, and the generated accessors are named after their strings -- so `import mantra.composeapp.generated.resources.translate` is a *prefix* of `...resources.translate_into_which_dialect`. The substring test decided the import was already there, and the compiler reported "Unresolved reference 'translate'" in a file whose imports looked complete. Both extractors now match whole lines, and the helper carries the explanation. **Four filters, each earned by something the dry run got wrong.** *A template that is only interpolation has nothing to translate.* `Text("$name")` would have become a resource holding `%1$s` -- longer, slower, and no more localisable than the code it replaced. *A leading or trailing space means it is being glued to a neighbour.* " \\u00b7 %1$s" is a separator. The test has to be on the format string rather than on the literal halves: a template opening with an interpolation leaves the first part empty and the second starting with the separating space, which makes "%1$s Key packages" look like a fragment when it is a whole label. *`\\uXXXX` and `\\"` are Kotlin syntax, not XML.* Left alone they would have shipped as the six visible characters of the escape. They are decoded into the resource, which is UTF-8 and can hold `·` directly. `\\n` is **not** decoded, because StringCatalogueJvmTest shows Compose Resources processes that one and a real newline in an XML value would be reflowed by the parser. *A term of a `+` concatenation is still not a string.* Same rule as the plain extractor. **Three copy problems surfaced only here, because interpolated strings had never been checked.** `m3-title-case.py` excludes anything containing `$` -- an interpolation is not a literal -- so `"$count Key Packages"` had been invisible to every pass so far, as had `"replying To ${…}"`. And a third instance of the old product name, in `"...once they're on Torch."`. All three fixed. Worth noting as a gap in the checker rather than a one-off: title case inside a template is still unchecked, and there are 83 concatenation fragments left where it could hide. **Two new assertions, on the two things a compiler cannot see.** Argument *order* is decided by where each `${…}` sat, and a transposition compiles and reads plausibly -- "Recovered 3 of 12" against "Recovered 12 of 3" -- so a two-argument and a three-argument string are asserted end to end. The three-argument one doubles as the check that `·` was decoded rather than passed through. **What is deliberately left.** 83 literals that are terms of a `+` concatenation. Reassembling `"a " + x + " b"` into one format string means deciding what the whole sentence is, and several are pluralisations -- `(if (n == 2) "event" else "events")` -- which want a real plural resource rather than a format argument, and that is an API choice rather than a rewrite. `m3-extract-formatted.py --remaining` lists them. **Tests.** 949 pass, 600 jvm over 73 classes and 349 android over 44, up from 947/598/349. `:composeApp:compileDebugKotlinAndroid` builds; the debug apk installs and runs on emulator-5554 through onboarding, the message list and a chat room with its text intact. `m3-audit.sh --check` exits 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:45:35 +02:00
<string name="add_chapter_to">Add chapter to %1$s</string>
<string name="add_people_to">Add people to %1$s</string>
<string name="chapter">Chapter %1$s</string>
<string name="chapter_words_characters">Chapter %1$s · %2$s words · %3$s characters</string>
<string name="chunk">Chunk %1$s</string>
<string name="chunks">Chunks (%1$s)</string>
<string name="chunks_translated">%1$s/%2$s chunks translated</string>
<string name="event_s_still_unreadable">%1$s event(s) still unreadable%2$s</string>
<string name="events_signed_together">%1$s events, signed together</string>
<string name="everyone_has_to_be_online_at_the_same_time">Everyone has to be online at the same time — the ceremony can only finish once all %1$s of you have taken part.</string>
<string name="functionality_coming_soon_2">"%1$s" functionality coming soon</string>
<string name="how_should_be_run">How should %1$s be run?</string>
<string name="invite_2">Invite %1$s</string>
<string name="key_packages">%1$s Key Packages</string>
<string name="next_with">Next with %1$s</string>
<string name="nostr">nostr:%1$s..</string>
<string name="nostr_2">nostr:%1$s...</string>
<string name="of">%1$s of %2$s</string>
<string name="of_members_will_be_needed_to_sign_with_this">%1$s of %2$s members will be needed to sign with this key.</string>
<string name="of_them_could_not_be_read">%1$s of them could not be read</string>
<string name="once_invited_will_be_able_to_receive_and">Once invited will be able to receive and send private message sent to all the %1$s members in the chat room.</string>
<string name="private_message_to">Private message to %1$s</string>
<string name="private_to">Private to %1$s</string>
<string name="proposals_are_waiting_for_your_signature">%1$s proposals are waiting for your signature</string>
<string name="recovered_of_event_s">Recovered %1$s of %2$s event(s)%3$s</string>
<string name="recovered_of_still_unreadable">Recovered %1$s of %2$s · %3$s still unreadable%4$s</string>
<string name="reply_privately_to">Reply privately to %1$s</string>
<string name="reply_to">Reply to %1$s</string>
<string name="replying_to">replying To %1$s</string>
<string name="searching_for_on">Searching for %1$s on "%2$s"</string>
<string name="selected">%1$s selected</string>
<string name="sent_a_private_message_to">%1$s sent a private message to %2$s</string>
<string name="shared_key_for">Shared key for %1$s</string>
<string name="to_join_the_chat_room">to join the %1$s chat room.</string>
<string name="translate">Translate %1$s</string>
<string name="unsupported_event_kind_2">Unsupported event kind: %1$s</string>
<string name="was_created_but_couldn_t_be_added_yet_invite">%1$s was created, but %2$s couldn't be added yet. Invite them again from the chat once they're on Torch.</string>
<string name="was_created_but_its_shared_key_ceremony">%1$s was created, but its shared key ceremony couldn't be started. Open the chat and start it from the group's details — until then the group has no key of its own.</string>
<string name="words_characters">%1$s words · %2$s characters</string>
<string name="you_and_others">You and %1$s others</string>
fix: sentence-case every UI string, settle the product name, and empty the dead catalogue Phase 4, first step, of docs/material-design-conformance.md. M3's style guide is unambiguous: "All text, including titles, headings, labels, menu items, navigation components, app bars, and buttons should use sentence-style capitalization. ... Don't use title case capitalization." The tree was title case throughout. **100 occurrences across 60 distinct strings**, in two passes, and the second pass is the interesting one. The first pass matched `[A-Z][a-z]+( [A-Z][a-z]+)+` in a `text =`, `Text(` or `contentDescription =` position and found 41 strings, 73 occurrences: "Add Chapter", "Sign In", "Key Package Management", "Publish New Key Package". Then the audit reported zero and the app still had "Invite a Friend" on its first screen. Two holes. The pattern required every word after the first to be capitalised, so anything with an article in it survived -- "Invite a Friend", "Add to Group", "Name of Artifact", "Sign in to Npub". And it read one line at a time, so a `Text(` whose literal sat on the next line was invisible. A whole-file scan allowing lowercase articles found 19 more strings, 27 occurrences. **Sample data is deliberately left in title case.** "Steve Biko", "John Doe", "Frank Talk", "To Kill a Mockingbird", "Man With A Plan", "Woman Of Few Words" are people and titles of works, and title case is how those are written. The first audit swept them up and reported 67 offenders where the real number was 41, which is the kind of number that teaches a reader to ignore the tool. Also untouched: the KDoc reference to iOS's own "Increase Contrast" setting, which is Apple's capitalisation of Apple's setting, and `logger.d("Queried Sync")`, which is written for whoever is reading logcat. **Two strings changed meaning rather than just case.** "Sign in to Npub" became "Sign in with an npub" -- npub is a protocol term, lowercase everywhere else in this app, and you sign in *with* one rather than *to* it. "Lightning Bolt", a content description, became "Lightning payment": M3's rule for a description is to name the purpose rather than the picture, and "bolt" is the picture. **The product has one name now, and it is Mantra.** The launcher label, the desktop window title, the landing screen and the package all said Mantra; the home screen's app bar said "Torch" and `composeResources`' `app_name` said "Machankura". The app bar is fixed. `UserAgent.APP_NAME` still says "Torch" and is left alone on purpose -- it goes on the wire to relay operators, so it is a network identity question rather than a content one, and a comment at the call site says so. **The two destructive actions now say what they do.** "Leave group" and "Delete group" are `TextButton`s that fire immediately, with no confirmation step and nothing stating the consequence. M3: "Tell users what will happen if they take an action and how they can undo it." Read out of the repository rather than guessed, because saying the wrong thing about a destructive action is worse than saying nothing. `leaveChatRoom` sets `leftGroupAt` and posts a line to the room; `softDeleteChatRoom` sets `deletedAt` on the local row and nothing else. So: "Posts a line to the room saying you left, and lets you delete it from this device afterwards", and "Removes the room from this device. The messages stay on the relays and with the other members." The second matters most -- a button labelled "Delete group" with no qualifier invites the belief that the messages are gone, which is the opposite of true. **1101 dead strings deleted.** `composeResources/values/strings.xml` held the phoenix wallet fork's whole catalogue -- notification channels, electrum settings, swap timeouts -- and **nothing referenced any of it**. The tree's only two `stringResource` calls are both commented out, and one of them names an `R.string`, which does not exist in a Compose Multiplatform resource set at all. Keeping them made the file look like the app's catalogue while the app's actual 332 strings sat in composables. It now holds `app_name` and a note about what happens next. A trap for the next person, recorded in the file: the compose resources plugin reports an XML comment containing a double hyphen only as "XML file ... is not valid. Check the file content." XML forbids `--` inside comments, and this commit hit it while writing that note. **The audit's check is now a script, for the reason the second pass exists.** `docs/scripts/m3-title-case.py` scans whole files, allows articles, excludes sample data by name and skips logger calls. Budget ratcheted to 0. The grep it replaces was wrong in three ways and reported success anyway, which is worse than not checking. **Tests.** 944 pass, 595 jvm over 72 classes and 349 android over 44, unchanged. The debug apk installs and runs on emulator-5554. `m3-audit.sh --check` exits 0. The 332 literals themselves are the next commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 01:27:26 +02:00
</resources>