fix(relay-auth): two copy defects the prompt only shows once it can fire

Both found on device once prompts started appearing again.

An unloaded counterparty rendered as a shortened npub: "It won't serve posts from
npub1j9hlsge8...kqy003h0 unless you log in." `User.toBestDisplayName()` falls back
to `pubkeyDisplayHex()`, so the sentence presented an unrecognizable key as if it
were a person's name — the one thing the reason line exists to supply. The
counterparty now falls back to the generic "someone you haven't loaded yet", which
says exactly as much and reads as language. The dialog *title* deliberately keeps
the npub: it names the account whose identity is about to be revealed, and there an
exact key beats a generic phrase.

"It's also holding back 1 other thing(s)." was a `<string>` with a parenthesised
plural, which res/CLAUDE.md rules out — the noun declines on count in the Slavic
and Semitic locales we ship. Now a `<plurals>`. It exists only in values/, so no
other locale needed converting.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vitor Pamplona
2026-08-12 12:40:52 -04:00
co-authored by Claude Opus 5
parent 0bddb64abc
commit bc3ce1139b
2 changed files with 36 additions and 3 deletions
@@ -51,6 +51,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
@@ -399,7 +400,7 @@ private fun secondaryLine(
): String? {
val others = purposes.filter { it !== primary }
if (others.isEmpty()) return null
return stringRes(R.string.relay_auth_also_holding_back, others.size)
return pluralStringResource(R.plurals.relay_auth_also_holding_back, others.size, others.size)
}
/** A short label for a set of counterparties: the first person's name, or "Alice and 4 others". */
@@ -409,10 +410,39 @@ private fun counterpartyLabel(
accountViewModel: AccountViewModel,
): String {
val first = pubkeys.firstOrNull() ?: return ""
val name = rememberDisplayName(first, accountViewModel)
val name = rememberCounterpartyName(first, accountViewModel)
return if (pubkeys.size > 1) stringRes(R.string.relay_auth_name_and_n_others, name, (pubkeys.size - 1).toString()) else name
}
/**
* Like [rememberDisplayName], but never renders a bare npub.
*
* The reason sentence exists to name *a person* — "it won't serve posts from Alice". A pubkey we
* have no metadata for has no name to give, and [User.toBestDisplayName] falls back to the shortened
* npub, so the sentence became "it won't serve posts from npub1j9hlsge8…kqy003h0 unless you log in":
* a string the reader cannot recognize, dressed up as if it were a name. Falling back to the generic
* phrase says the same amount and reads as language.
*
* The dialog *title* deliberately keeps the npub ([rememberDisplayName]): it names the account whose
* identity is about to be revealed, where an unrecognizable-but-exact key still beats "someone you
* haven't loaded yet" — the user can at least match it against the account they are logged in as.
*/
@Composable
private fun rememberCounterpartyName(
pubkey: HexKey,
accountViewModel: AccountViewModel,
): String {
var user by remember(pubkey) { mutableStateOf(accountViewModel.getUserIfExists(pubkey)) }
if (user == null) {
LaunchedEffect(pubkey) { user = accountViewModel.checkGetOrCreateUser(pubkey) }
}
val loaded = user ?: return stringRes(R.string.relay_auth_someone_unloaded)
val metadata by observeUserInfo(loaded, accountViewModel)
return metadata?.info?.bestName()
?: loaded.metadataOrNull()?.bestName()
?: stringRes(R.string.relay_auth_someone_unloaded)
}
/**
* A display name for a venue id — a public chat channel (64-hex event id), a NIP-53 live activity,
* or a NIP-72 community.
+4 -1
View File
@@ -1165,7 +1165,10 @@
<string name="relay_auth_why_other">It asks everyone to log in. Amethyst can\'t tell what it\'s holding back.</string>
<string name="relay_auth_someone_unloaded">someone you haven\'t loaded yet</string>
<string name="relay_auth_name_and_n_others">%1$s and %2$s others</string>
<string name="relay_auth_also_holding_back">It\'s also holding back %1$d other thing(s).</string>
<plurals name="relay_auth_also_holding_back">
<item quantity="one">It\'s also holding back %1$d other thing.</item>
<item quantity="other">It\'s also holding back %1$d other things.</item>
</plurals>
<string name="relay_auth_remember_relay">Remember for this relay</string>
<string name="relay_auth_log_in">Log in</string>
<string name="relay_auth_not_now">Not now</string>