refactor: make the 40 interpolated UI strings format strings, and assert the argument order
Phase 4, third step, of docs/material-design-conformance.md. `Text("Add chapter to
${uiState.artifact.name}")` becomes a resource holding `Add chapter to %1$s` and a call
passing the expression. 49 call sites. Literals in composables go 76 -> 39;
`stringResource` goes 374 -> 424.
**A silent bug in the previous commit's extractor, found by this one.** Imports were
tested with `statement in source`, and the generated accessors are named after their
strings -- so `import mantra.composeapp.generated.resources.translate` is a *prefix* of
`...resources.translate_into_which_dialect`. The substring test decided the import was
already there, and the compiler reported "Unresolved reference 'translate'" in a file
whose imports looked complete. Both extractors now match whole lines, and the helper
carries the explanation.
**Four filters, each earned by something the dry run got wrong.**
*A template that is only interpolation has nothing to translate.* `Text("$name")` would
have become a resource holding `%1$s` -- longer, slower, and no more localisable than the
code it replaced.
*A leading or trailing space means it is being glued to a neighbour.* " \\u00b7 %1$s" is a
separator. The test has to be on the format string rather than on the literal halves: a
template opening with an interpolation leaves the first part empty and the second starting
with the separating space, which makes "%1$s Key packages" look like a fragment when it is
a whole label.
*`\\uXXXX` and `\\"` are Kotlin syntax, not XML.* Left alone they would have shipped as the
six visible characters of the escape. They are decoded into the resource, which is UTF-8
and can hold `·` directly. `\\n` is **not** decoded, because
StringCatalogueJvmTest shows Compose Resources processes that one and a real newline in an
XML value would be reflowed by the parser.
*A term of a `+` concatenation is still not a string.* Same rule as the plain extractor.
**Three copy problems surfaced only here, because interpolated strings had never been
checked.** `m3-title-case.py` excludes anything containing `$` -- an interpolation is not a
literal -- so `"$count Key Packages"` had been invisible to every pass so far, as had
`"replying To ${…}"`. And a third instance of the old product name, in
`"...once they're on Torch."`. All three fixed. Worth noting as a gap in the checker rather
than a one-off: title case inside a template is still unchecked, and there are 83
concatenation fragments left where it could hide.
**Two new assertions, on the two things a compiler cannot see.** Argument *order* is
decided by where each `${…}` sat, and a transposition compiles and reads plausibly --
"Recovered 3 of 12" against "Recovered 12 of 3" -- so a two-argument and a three-argument
string are asserted end to end. The three-argument one doubles as the check that `·`
was decoded rather than passed through.
**What is deliberately left.** 83 literals that are terms of a `+` concatenation.
Reassembling `"a " + x + " b"` into one format string means deciding what the whole
sentence is, and several are pluralisations -- `(if (n == 2) "event" else "events")` --
which want a real plural resource rather than a format argument, and that is an API choice
rather than a rewrite. `m3-extract-formatted.py --remaining` lists them.
**Tests.** 949 pass, 600 jvm over 73 classes and 349 android over 44, up from 947/598/349.
`:composeApp:compileDebugKotlinAndroid` builds; the debug apk installs and runs on
emulator-5554 through onboarding, the message list and a chat room with its text intact.
`m3-audit.sh --check` exits 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -272,4 +272,45 @@
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user